# M0 Documentation
import { Callout } from 'vocs/components';
## WrappedMToken
### Code
WrappedMToken is an ERC20 token for wrapping the $M token into a non-rebasing token with claimable yields.
Solidity - Github Repository -
[m0-foundation/WrappedMToken.sol](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol)
### Token Overview
#### Account Struct
The WrappedMToken uses a special account struct to track the state of each user's balance:
```solidity
struct Account {
// First Slot
bool isEarning;
uint240 balance;
// Second slot
uint112 earningPrincipal;
bool hasClaimRecipient;
bool hasEarnerDetails;
}
```
This struct serves multiple purposes:
* `isEarning`: Tracks whether the account is earning yield
* `balance`: Represents the token balance held by the account
* `earningPrincipal`: Tracks the principal amount for yield calculation
* `hasClaimRecipient`: Indicates if account has a custom claim recipient
* `hasEarnerDetails`: Indicates if account has additional earning parameters
#### Key State Variables
```solidity
uint16 public constant HUNDRED_PERCENT = 10_000;
bytes32 public constant EARNERS_LIST_IGNORED_KEY = "earners_list_ignored";
bytes32 public constant EARNERS_LIST_NAME = "earners";
bytes32 public constant CLAIM_OVERRIDE_RECIPIENT_KEY_PREFIX = "wm_claim_override_recipient";
bytes32 public constant MIGRATOR_KEY_PREFIX = "wm_migrator_v2";
address public immutable earnerManager;
address public immutable migrationAdmin;
address public immutable mToken;
address public immutable registrar;
address public immutable excessDestination;
uint112 public totalEarningPrincipal;
uint240 public totalEarningSupply;
uint240 public totalNonEarningSupply;
mapping(address account => Account balance) internal _accounts;
uint128 public enableMIndex;
uint128 public disableIndex;
int240 public roundingError;
mapping(address account => address claimRecipient) internal _claimRecipients;
```
These variables track the essential state of the token system:
* Constants for percentage calculations and registry keys
* `earnerManager`: Address of the manager governing earning eligibility
* `migrationAdmin`: Address allowed to perform migrations
* `mToken`: Address of the underlying $M token
* `registrar`: Address of the Registrar for configuration
* `excessDestination`: Address where excess tokens are sent
* Supply tracking for earning and non-earning tokens
* Index tracking for earning calculations
* `roundingError`: Tracking imprecise $M token transfers
### Functions
#### constructor
```solidity
constructor(
address mToken_,
address registrar_,
address earnerManager_,
address excessDestination_,
address migrationAdmin_
) ERC20Extended("M (Wrapped) by M0", "wM", 6)
```
Initializes the token with:
* Name: "M (Wrapped) by M0"
* Symbol: "wM"
* Decimals: 6
* References to $M Token, Registrar, Earner Manager, and other essential contracts
##### Parameters:
| Name | Type | Description |
| :------------------- | :------ | :---------------------------------- |
| `mToken_` | address | The $M Token contract address |
| `registrar_` | address | The Registrar contract address |
| `earnerManager_` | address | The Earner Manager contract address |
| `excessDestination_` | address | The destination for excess tokens |
| `migrationAdmin_` | address | The migration admin address |
#### wrap
```solidity
function wrap(address recipient_, uint256 amount_) external
```
Wraps $M tokens into wrapped $M tokens.
* Transfers $M tokens from the caller to this contract
* Mints wrapped $M tokens to the recipient
##### Parameters:
| Name | Type | Description |
| :----------- | :------ | :---------------------------------- |
| `recipient_` | address | The recipient of the wrapped tokens |
| `amount_` | uint256 | The amount of $M tokens to wrap |
#### wrapWithPermit
```solidity
function wrapWithPermit(
address recipient_,
uint256 amount_,
uint256 deadline_,
uint8 v_,
bytes32 r_,
bytes32 s_
) external
```
Wraps $M tokens using EIP-2612 permit for approval.
##### Parameters:
| Name | Type | Description |
| :----------- | :------ | :---------------------------------- |
| `recipient_` | address | The recipient of the wrapped tokens |
| `amount_` | uint256 | The amount of $M tokens to wrap |
| `deadline_` | uint256 | Expiration timestamp for the permit |
| `v_` | uint8 | ECDSA signature component |
| `r_` | bytes32 | ECDSA signature component |
| `s_` | bytes32 | ECDSA signature component |
#### wrapWithPermit (alternative)
```solidity
function wrapWithPermit(
address recipient_,
uint256 amount_,
uint256 deadline_,
bytes memory signature_
) external
```
Wraps $M tokens using arbitrary signature format.
##### Parameters:
| Name | Type | Description |
| :----------- | :------ | :---------------------------------- |
| `recipient_` | address | The recipient of the wrapped tokens |
| `amount_` | uint256 | The amount of $M tokens to wrap |
| `deadline_` | uint256 | Expiration timestamp for the permit |
| `signature_` | bytes | Arbitrary signature data |
#### unwrap
```solidity
function unwrap(address recipient_, uint256 amount_) external
```
Unwraps wrapped $M tokens back to $M tokens.
* Burns wrapped $M tokens from the caller
* Transfers $M tokens to the recipient
##### Parameters:
| Name | Type | Description |
| :----------- | :------ | :------------------------------------ |
| `recipient_` | address | The recipient of the unwrapped tokens |
| `amount_` | uint256 | The amount of tokens to unwrap |
#### claimFor
```solidity
function claimFor(address account_) external returns (uint240 yield_)
```
Claims accrued yield for an account.
* Only affects accounts in earning mode
* Routes yield to configured claim recipient
* Handles fee distribution if earner details exist
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :----------------------------- |
| `account_` | address | The account to claim yield for |
##### Return Values:
| Name | Type | Description |
| :------- | :------ | :-------------------------- |
| `yield_` | uint240 | The amount of yield claimed |
#### claimExcess
```solidity
function claimExcess() external returns (uint240 claimed_)
```
Claims any excess $M tokens that aren't earmarked for balances.
* Verifies excess exists before claiming
* Transfers excess to the configured destination
##### Return Values:
| Name | Type | Description |
| :--------- | :------ | :---------------------------------- |
| `claimed_` | uint240 | The amount of excess tokens claimed |
#### enableEarning
```solidity
function enableEarning() external
```
Enables yield earning for the contract.
* Verifies contract is approved as an earner
* Records the current $M index as the reference point
* Calls `startEarning` on the $M token
#### disableEarning
```solidity
function disableEarning() external
```
Disables yield earning for the contract.
* Verifies contract is not approved as an earner
* Records the current index as the disable point
* Calls `stopEarning` on the $M token
#### startEarningFor
```solidity
function startEarningFor(address account_) external
```
Transitions an account to earning mode.
* Verifies earning is enabled globally
* Checks if account is an approved earner
* Updates account and global state variables
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :--------------------------- |
| `account_` | address | The account to start earning |
#### startEarningFor (batch)
```solidity
function startEarningFor(address[] calldata accounts_) external
```
Transitions multiple accounts to earning mode.
* Processes each account individually
##### Parameters:
| Name | Type | Description |
| :---------- | :--------- | :---------------------------- |
| `accounts_` | address\[] | The accounts to start earning |
#### stopEarningFor
```solidity
function stopEarningFor(address account_) external
```
Transitions an account to non-earning mode.
* Claims any accrued yield
* Updates account and global state variables
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :-------------------------- |
| `account_` | address | The account to stop earning |
#### stopEarningFor (batch)
```solidity
function stopEarningFor(address[] calldata accounts_) external
```
Transitions multiple accounts to non-earning mode.
* Processes each account individually
##### Parameters:
| Name | Type | Description |
| :---------- | :--------- | :--------------------------- |
| `accounts_` | address\[] | The accounts to stop earning |
#### setClaimRecipient
```solidity
function setClaimRecipient(address claimRecipient_) external
```
Sets a custom recipient for yield claims.
* Updates the claim recipient mapping
* Sets a flag on the account for optimization
##### Parameters:
| Name | Type | Description |
| :---------------- | :------ | :----------------------------- |
| `claimRecipient_` | address | The recipient to receive yield |
#### migrate
```solidity
function migrate(address migrator_) external
```
Performs arbitrary migration logic.
* Only callable by the migration admin
* Delegates to migrator contract
##### Parameters:
| Name | Type | Description |
| :---------- | :------ | :------------------------------------------- |
| `migrator_` | address | The contract that will perform the migration |
### View/Pure Functions
#### accruedYieldOf
```solidity
function accruedYieldOf(address account_) public view returns (uint240 yield_)
```
Returns the yield accrued by an account.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :------- | :------ | :-------------------------------- |
| `yield_` | uint240 | The accrued yield for the account |
#### balanceOf
```solidity
function balanceOf(address account_) public view returns (uint256 balance_)
```
Returns the token balance of an account.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :--------- | :------ | :-------------------- |
| `balance_` | uint256 | Current token balance |
#### balanceWithYieldOf
```solidity
function balanceWithYieldOf(address account_) external view returns (uint256 balance_)
```
Returns the token balance plus accrued yield.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :--------- | :------ | :------------------------------ |
| `balance_` | uint256 | Balance including accrued yield |
#### earningPrincipalOf
```solidity
function earningPrincipalOf(address account_) external view returns (uint112 earningPrincipal_)
```
Returns the principal amount for earning accounts.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :------------------ | :------ | :------------------------------------------ |
| `earningPrincipal_` | uint112 | Principal amount used for yield calculation |
#### claimRecipientFor
```solidity
function claimRecipientFor(address account_) public view returns (address recipient_)
```
Returns the recipient for an account's yield.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :----------- | :------ | :----------------------------------- |
| `recipient_` | address | Address to receive the claimed yield |
#### currentIndex
```solidity
function currentIndex() public view returns (uint128 index_)
```
Returns the current index for yield calculations.
##### Return Values:
| Name | Type | Description |
| :------- | :------ | :------------------ |
| `index_` | uint128 | Current index value |
#### isEarning
```solidity
function isEarning(address account_) external view returns (bool isEarning_)
```
Checks if an account is in earning mode.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :----------- | :--- | :----------------------------------- |
| `isEarning_` | bool | Whether the account is earning yield |
#### isEarningEnabled
```solidity
function isEarningEnabled() public view returns (bool isEnabled_)
```
Checks if earning is enabled contract-wide.
##### Return Values:
| Name | Type | Description |
| :----------- | :--- | :-------------------------------- |
| `isEnabled_` | bool | Whether global earning is enabled |
#### excess
```solidity
function excess() public view returns (int240 excess_)
```
Calculates excess $M tokens held by the contract.
##### Return Values:
| Name | Type | Description |
| :-------- | :----- | :------------------------------------------- |
| `excess_` | int240 | Amount of excess $M tokens (can be negative) |
#### totalAccruedYield
```solidity
function totalAccruedYield() external view returns (uint240 yield_)
```
Calculates total yield accrued across all accounts.
##### Return Values:
| Name | Type | Description |
| :------- | :------ | :------------------ |
| `yield_` | uint240 | Total accrued yield |
#### totalSupply
```solidity
function totalSupply() external view returns (uint256 totalSupply_)
```
Returns the total supply of wrapped tokens.
##### Return Values:
| Name | Type | Description |
| :------------- | :------ | :------------------------- |
| `totalSupply_` | uint256 | Total supply of all tokens |
#### projectedEarningSupply
```solidity
function projectedEarningSupply() public view returns (uint240 supply_)
```
Calculates projected total of earning tokens including yield.
##### Return Values:
| Name | Type | Description |
| :-------- | :------ | :-------------------------------- |
| `supply_` | uint240 | Projected total of earning tokens |
### Key Internal Functions
#### \_mint/\_burn
Internal functions to mint and burn tokens, handling both earning and non-earning accounts.
#### \_wrap/\_unwrap
Internal functions to handle the wrapping and unwrapping of $M tokens.
#### \_claim
Internal function to calculate and distribute accrued yield.
#### \_transfer
Internal override of the ERC20 transfer function handling transfers between different account types.
#### \_startEarningFor/\_stopEarningFor
Internal functions to handle transitioning accounts between earning and non-earning modes.
#### \_getAccruedYield
Calculates the yield given an account's balance, earning principal, and current index.
#### \_handleEarnerDetails
Calculates and transfers fees for accounts with earner details.
### Events
```solidity
event Claimed(address indexed account, address indexed recipient, uint240 yield);
event ClaimRecipientSet(address indexed account, address indexed claimRecipient);
event EarningEnabled(uint128 index);
event EarningDisabled(uint128 index);
event ExcessClaimed(uint240 excess);
event StartedEarning(address indexed account);
event StoppedEarning(address indexed account);
```
* `Claimed`: Emitted when yield is claimed for an account
* `ClaimRecipientSet`: Emitted when a claim recipient is set
* `EarningEnabled`: Emitted when earning is enabled contract-wide
* `EarningDisabled`: Emitted when earning is disabled contract-wide
* `ExcessClaimed`: Emitted when excess tokens are claimed
* `StartedEarning`: Emitted when an account starts earning
* `StoppedEarning`: Emitted when an account stops earning
### Custom Errors
```solidity
error EarningIsDisabled();
error EarningIsEnabled();
error IsApprovedEarner(address account);
error InsufficientBalance(address account, uint240 balance, uint240 amount);
error NotApprovedEarner(address account);
error NoExcess();
error UnauthorizedMigration();
error ZeroEarnerManager();
error ZeroExcessDestination();
error ZeroMToken();
error ZeroMigrationAdmin();
error ZeroRegistrar();
```
These custom errors provide clear signals about issues during operations.
import { Callout } from "vocs/components";
## MToken
### Code
MToken is an ERC20 token implementing a dual-balance accounting system, allowing users to choose between non-earning and earning balances.
Solidity - Github Repository -
[m0-foundation/MToken.sol](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol)
### Token Overview
#### MBalance Struct
The MToken uses a special balance struct to track the state of each account's balance:
```solidity
struct MBalance {
bool isEarning;
uint240 rawBalance;
}
```
This struct serves dual purposes:
* For non-earning accounts: `rawBalance` represents the actual token balance
* For earning accounts: `rawBalance` represents the principal amount, which grows over time via indexing
#### Key State Variables
```solidity
address public immutable minterGateway;
address public immutable ttgRegistrar;
uint240 public totalNonEarningSupply;
uint112 public principalOfTotalEarningSupply;
mapping(address account => MBalance balance) internal _balances;
```
These variables track the essential state of the token system:
* `minterGateway`: authorized address for minting and burning
* `ttgRegistrar`: address of the TTG Registrar that governs earnings eligibility
* `totalNonEarningSupply`: total supply of non-earning tokens
* `principalOfTotalEarningSupply`: principal amount of earning tokens
* `_balances`: mapping of account balances
### Functions
#### constructor
```solidity
constructor(address ttgRegistrar_, address minterGateway_) ContinuousIndexing() ERC20Extended("M by M0", "M", 6)
```
Initializes the token with:
* Name: "M by M0"
* Symbol: "M"
* Decimals: 6
* TTG Registrar and Minter Gateway addresses
##### Parameters:
| Name | Type | Description |
| :--------------- | :------ | :---------------------------------- |
| `ttgRegistrar_` | address | The TTG Registrar contract address |
| `minterGateway_` | address | The Minter Gateway contract address |
#### mint
```solidity
function mint(address account_, uint256 amount_) external onlyMinterGateway
```
Mints new tokens to the specified account.
* Only callable by the minter gateway
* Handles both earning and non-earning accounts differently
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :--------------------------- |
| `account_` | address | The recipient of the tokens |
| `amount_` | uint256 | The amount of tokens to mint |
#### burn
```solidity
function burn(address account_, uint256 amount_) external onlyMinterGateway
```
Burns tokens from the specified account.
* Only callable by the minter gateway
* Handles both earning and non-earning accounts differently
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------------------ |
| `account_` | address | The account to burn tokens from |
| `amount_` | uint256 | The amount of tokens to burn |
#### startEarning
```solidity
function startEarning() external
```
Transitions the caller's account to earning mode.
* Converts non-earning balance to principal
* Reverts if caller is not approved as an earner by TTG Registrar
* Emits a `StartedEarning` event
#### stopEarning (self)
```solidity
function stopEarning() external
```
Transitions the caller's account to non-earning mode.
* Converts principal plus accrued interest to a standard balance
* Emits a `StoppedEarning` event
#### stopEarning (for other account)
```solidity
function stopEarning(address account_) external
```
Transitions a specified account to non-earning mode.
* Reverts if the account is approved as an earner by TTG Registrar
* Useful for administrative operations or governance
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------------------ |
| `account_` | address | The account to stop earning for |
#### updateIndex
```solidity
function updateIndex() public virtual returns (uint128 currentIndex_)
```
Updates the stored index based on time elapsed and current rate.
* Fetches the latest rate from the rate model
* Emits an `IndexUpdated` event
##### Return Values:
| Name | Type | Description |
| :-------------- | :------ | :------------------ |
| `currentIndex_` | uint128 | The new index value |
#### rateModel
```solidity
function rateModel() public view returns (address rateModel_)
```
Returns the address of the rate model contract.
##### Return Values:
| Name | Type | Description |
| :----------- | :------ | :---------------------------- |
| `rateModel_` | address | The address of the rate model |
#### earnerRate
```solidity
function earnerRate() public view returns (uint32 earnerRate_)
```
Returns the current rate used for interest calculations.
##### Return Values:
| Name | Type | Description |
| :------------ | :----- | :--------------------------- |
| `earnerRate_` | uint32 | Current rate in basis points |
#### totalEarningSupply
```solidity
function totalEarningSupply() public view returns (uint240 totalEarningSupply_)
```
Calculates the current total of all earning tokens including accrued interest.
##### Return Values:
| Name | Type | Description |
| :-------------------- | :------ | :----------------------------- |
| `totalEarningSupply_` | uint240 | Total amount of earning tokens |
#### totalSupply
```solidity
function totalSupply() external view returns (uint256 totalSupply_)
```
Returns the sum of total non-earning supply and total earning supply.
##### Return Values:
| Name | Type | Description |
| :------------- | :------ | :------------------------- |
| `totalSupply_` | uint256 | Total supply of all tokens |
#### principalBalanceOf
```solidity
function principalBalanceOf(address account_) external view returns (uint240 balance_)
```
Returns the principal amount for earning accounts.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :--------- | :------ | :------------------------------------------- |
| `balance_` | uint240 | Principal balance if earning, otherwise zero |
#### balanceOf
```solidity
function balanceOf(address account_) external view returns (uint256 balance_)
```
Returns the token balance of an account.
* For earning accounts: Returns principal with accrued interest
* For non-earning accounts: Returns raw balance
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :--------- | :------ | :-------------------- |
| `balance_` | uint256 | Current token balance |
#### isEarning
```solidity
function isEarning(address account_) external view returns (bool isEarning_)
```
Checks if an account is in earning mode.
##### Parameters:
| Name | Type | Description |
| :--------- | :------ | :------------------- |
| `account_` | address | The account to check |
##### Return Values:
| Name | Type | Description |
| :----------- | :--- | :------------------------------------- |
| `isEarning_` | bool | Whether the account is in earning mode |
#### currentIndex
```solidity
function currentIndex() public view override returns (uint128)
```
Calculates the current index based on the rate and time elapsed.
##### Return Values:
| Name | Type | Description |
| :-------------- | :------ | :------------------ |
| `currentIndex_` | uint128 | Current index value |
### Key Internal Functions
#### \_transfer
Internal override of the ERC20 transfer function handling transfers between earning and non-earning accounts.
#### \_startEarning/\_stopEarning
Internal functions to handle transitioning accounts between earning and non-earning modes.
#### \_getPresentAmount
Calculates the present value of a principal amount using the current or specified index.
#### \_getPrincipalAmount
Calculates the principal amount from a present value using rounding favoring the protocol.
#### \_isApprovedEarner
Checks if an account is approved to earn by the TTG Registrar.
#### \_rate
Retrieves the current rate from the rate model contract.
### Events
```solidity
event StartedEarning(address indexed account);
event StoppedEarning(address indexed account);
event IndexUpdated(uint128 indexed index, uint32 indexed rate);
```
* `StartedEarning`: Emitted when an account transitions to earning mode
* `StoppedEarning`: Emitted when an account transitions to non-earning mode
* `IndexUpdated`: Emitted when the index is updated with a new value
### Custom Errors
```solidity
error InsufficientBalance(address account, uint256 rawBalance, uint256 amount);
error IsApprovedEarner();
error NotApprovedEarner();
error NotMinterGateway();
error OverflowsPrincipalOfTotalSupply();
error ZeroMinterGateway();
error ZeroTTGRegistrar();
```
These custom errors provide clear signals about issues during operations.
import { HomePage } from 'vocs/components'
My Awesome DocsThis is a description of my documentation website.Get startedGitHub
## Upgrades and Migration
### I. Key Improvements in V2
Wrapped $M (wM) fundamentally operates as a non-rebasing token where yield is realized through manual claiming, a
characteristic distinct from the native $M token's automatic rebasing. The V2 implementation builds upon this
established model with several important enhancements:
1. **Re-enabling Earning**: V2 allows earning to be re-enabled after being disabled
2. **Admin-Managed Earners**: Introduction of EarnerManager for delegated administration
3. **Fee Mechanism**: Ability for admins to take fees from yield
4. **Principal-Based Accounting**: Changed from lastIndex tracking to principal amount tracking
5. **Explicit Claim Recipients**: Added ability for users to explicitly set yield recipients
6. **Gas Efficiency**: Optimized storage layout and operations
### II. Migration from V1
The migration from V1 to V2 involved several key transformations:
#### Account Model Change
* V1: Used last interaction index for earning accounts
* V2: Uses principal amount for earning accounts
* Migration formula: `principal = balance ÷ lastIndex`
#### Index Handling
* V1: Used an array for enable/disable indices
* V2: Uses enableMIndex and disableIndex slots
* Migration preserved earning state and index values
#### Migration Process
The migration was handled through a specialized migrator contract that:
1. Identified all earning accounts in V1
2. Converted their account structure to the V2 format
3. Preserved their earning status and equivalent yield position
## Technical Design
### I. Account Model
The wM contract uses a sophisticated account model to efficiently track balances and yield information:
```solidity
struct Account {
bool isEarning; // Whether the account is in earning mode
uint240 balance; // Current token balance (excluding yield)
uint112 earningPrincipal; // Principal amount for earning accounts
bool hasClaimRecipient; // Whether a custom recipient is set
bool hasEarnerDetails; // Whether admin fee settings exist
}
```
This structure packs related data together to minimize storage costs while maintaining all necessary information for the
dual-mode system.
### II. Principal-Based Accounting
A key aspect of wM's design is its principal-based accounting system for earning accounts:
#### Principal Conversions
When an account starts earning, its balance is converted to a principal amount:
```
principal = balance ÷ currentIndex (rounded down)
```
This principal remains constant while the account is earning, while its value in tokens grows with the index.
#### Present Value Calculation
The current token value of a principal is:
```
presentValue = principal × currentIndex
```
This calculation is used when checking balances and claiming yield.
#### Rounding Strategy
The contract employs consistent rounding rules:
* When converting from tokens to principal (adding to earning), principal is rounded DOWN
* When calculating present value (for balance displays), the value is exact
* When subtracting from earning accounts, principal is rounded UP
These rules slightly favor the protocol, creating a small buffer of excess tokens that enhances stability.
### III. Supply Tracking
The contract maintains precise tracking of token supply:
* `totalNonEarningSupply`: Sum of all non-earning account balances
* `totalEarningSupply`: Sum of all earning account balances (excluding accrued yield)
* `totalEarningPrincipal`: Sum of all earning account principals
* Total supply = `totalNonEarningSupply + totalEarningSupply`
* `projectedEarningSupply`: The total earning supply if all accrued yield was claimed
These values are used for various calculations, including excess determination.
### IV. Transfer Mechanics
The transfer system handles different scenarios based on account earning status:
#### Same Status Transfers
* Between earning accounts: Converts the amount to principal using the current index
* Between non-earning accounts: Simple balance transfers without conversion
#### Cross-Status Transfers
* From earning to non-earning: Converts from principal to tokens
* From non-earning to earning: Converts from tokens to principal
* Affects total earning and non-earning supply tracking
Each transfer type ensures that exact token amounts move between accounts while maintaining proper accounting in the
appropriate mode.
### V. Fee Mechanism
When enabled by an admin through the EarnerManager, fees can be taken from yield:
* Fees are a percentage (in basis points, max 10,000) of claimed yield
* Fee amounts are transferred to the admin who approved the account
* If an admin is removed from the admin list, their fee settings become invalid
* Fees are processed during the claim operation
This creates an incentive for admins to onboard and manage accounts in the system.
\[//]:
##
'TODO: Diagram showing the flow of tokens during a claim operation with fees, including the original account, claim recipient, and admin fee recipient.'
### VI. Excess Mechanism
The `WrappedMToken` (wM) contract is designed to naturally accumulate "excess" $M tokens over time. This excess
represents the $M tokens held by the contract *above and beyond* what is required to fully back all circulating wM
tokens and their accrued yield entitlements. This mechanism enhances system stability and creates a value capture
opportunity for the protocol.
**Core Principle: Yield Mismatch**
The fundamental driver of excess accumulation stems from a mismatch in yield earning:
1. **wM Contract Earns $M Yield:** As confirmed by its status as a major $M holder and its requirement to be an approved
M earner (`enableEarning` requires `_isThisApprovedEarner`), the **entire balance of $M tokens held within the
`WrappedMToken` contract continuously earns yield** according to the $M token's native rebasing mechanism.
2. **Not All wM Holders Earn wM Yield:** However, only wM holders whose accounts are explicitly set to **Earning Mode**
(via Governance or EarnerManager approval) accrue a corresponding claimable wM yield entitlement based on their
principal. Holders in **Non-Earning Mode** do not accrue wM yield.
**Sources of Excess Accumulation:**
The excess $M balance primarily grows from:
1. **Yield on $M Backing Non-Earning wM (Primary Source):** The $M yield generated by the portion of $M tokens backing
the wM held by users in Non-Earning Mode directly contributes to the excess. Since these users aren't entitled to wM
yield, the $M yield earned on their underlying share remains within the contract as surplus.
2. **Rounding Effects during Principal Conversions (Secondary Source & Potential Deficit):** The contract utilizes
principal-based accounting for earners, requiring conversions between token amounts and principal values whenever
balances change (e.g., `wrap`, `unwrap`, `startEarningFor`, `stopEarningFor`, cross-status transfers). This process
involves rounding:
* Tokens to Principal (e.g., starting to earn): Principal is rounded **DOWN** (`getPrincipalAmountRoundedDown`).
* Subtracting Principal (e.g., stopping earning, transfers out): The principal equivalent of the token amount removed
is rounded **UP** (`getPrincipalAmountRoundedUp`) before being deducted.
* Yield Calculation (`_getAccruedYield`): The potential yield payout is based on a present value calculation rounded
**DOWN** (`getPresentAmountRoundedDown`).
* Calculating Liabilities (`projectedEarningSupply`): The present value of earning principals is rounded **UP**.
While some rounding directions favor the protocol (contributing positively to excess over time), others (like
subtracting principal rounded up during transfers/unwraps) can slightly disadvantage the protocol.
The combination of wM's internal rounding necessities with the behavior of the underlying $M token during external
transfers (wrapping/unwrapping M) can lead to temporary discrepancies. Specifically, if the $M token's transfer
mechanism results in the wM contract sending slightly more $M or receiving slightly less $M than the nominal amount
due to M's own rebasing logic, the wM contract might temporarily hold slightly less $M than strictly required by its
wM liabilities (a potential negative excess). The system is designed such that the primary sources of excess $M (like
yield on $M backing non-earning wM and wM's own favorable internal rounding) act as a substantial buffer to cover
these minor external interaction effects over time, ensuring long-term solvency.
3. **Direct $M Transfers:** Any $M tokens sent directly to the `WrappedMToken` contract address (outside the `wrap`
function) also become part of the excess.
4. **Cross-Status Transfers:** While involving rounding (covered above), the mechanics of transfers between earning and
non-earning accounts can also contribute minor gains due to the specific rounding applied during the principal
conversions involved.
**Calculating Excess:**
The contract calculates the available excess using the `excess()` view function, which basically can be computed like
this:
```math
\text{Excess}_{(\text{wM contract})} = \text{M Balance}_{(\text{wM contract})} - \underbrace{\text{(totalNonEarningSupply} + \text{ProjectedEarningSupply)}}_{\text{Total wM Liabilities}}
```
This calculation ensures that `excess` only represents $M tokens available *after* accounting for all obligations to wM
holders (both earning and non-earning).
**Excess Management:**
The accumulated excess $M tokens can be claimed and transferred out of the contract via the `claimExcess()` function.
The `excessDestination` is set during deployment and typically points to the **Distribution Vault**, creating a revenue
stream for the broader protocol ecosystem, ultimately benefiting Zero token holders.
**Relationship with Shared Earning Approval:**
Crucially, the ability for a wM holder to be in Earning Mode depends on the **same approval mechanisms (Governance list
or EarnerManager admin approval) used by the $M token system itself**. An account must be approved in
the EarnerManager (or globally via Governance) to call startEarningFor and begin accruing wM yield. This shared approval
layer is fundamental to the excess mechanism, as it governs *who* receives wM yield, directly influencing how much of
the $M yield generated by the wM contract contributes to the excess pool versus being earmarked for wM earners.
## Security considerations
### I. Trust Model
The wM system operates with several trust assumptions:
* **MigrationAdmin**: Fully trusted role that can change contract implementation
* **EarnerManager MigrationAdmin**: Can change EarnerManager implementation
* **Registrar**: Source of truth for system parameters
* **MToken**: Assumed to function correctly
* **Admins**: Trusted to manage earning status appropriately
### II. Edge Cases
Several edge cases are worth understanding:
#### Admin Removal
If an admin is removed from the admins list:
* Accounts they approved lose earning status
* Their fee settings become invalid
* Accounts should be stopped from earning to prevent further yield accrual
#### Disabled Earning
If the wrapper contract is removed from M's earner list:
* `disableEarning()` should be called to prevent further yield accrual
* Individual accounts can continue to earn until that point
#### Balance Precision
The system has specific precision limitations:
* All balances limited to uint240 to match $M token
* Principal amounts limited to uint112 for gas efficiency
* Indices use uint128 with 12 decimal places of precision
### III. Rounding Effects
The consistent rounding strategy creates specific effects:
* Multiple conversions between earning and non-earning states will result in small token losses
* Transfers between accounts with different earning statuses create small protocol gains
* These tiny amounts accumulate as protocol reserves, enhancing stability
import ZoomableImage from '@/components/ZoomableImage';
## Overview
### What is Wrapped $M (wM)?
Wrapped $M (wM) is a non-rebasing ERC-20 wrapper for the rebasing and immutable $M token. It maintains yield-earning
capabilities while providing compatibility with DeFi protocols that require standard ERC-20 tokens.
While the native $M token automatically increases balances as yield accrues, Wrapped $M maintains static balances where
yield can be explicitly claimed. This makes Wrapped $M compatible with protocols that aren't designed to handle rebasing
tokens, dramatically expanding the utility of $M in the broader DeFi ecosystem.
### Why Wrapped M?
Every M0 extension eventually wants to connect to certain utilities such as DeFi protocols, on/off ramps, etc. In order
to concentrate liquidity, that utility should always exist in the form of M0's stablecoin building block.
However, rebasing tokens automatically change their balances as yield accrues, which creates challenges for many DeFi
protocols that expect static token balances. Wrapped $M solves this by:
1. Maintaining fixed token balances that don't automatically increase
2. Separately tracking and allowing manual claiming of accrued yield
3. Preserving the full economic benefits of the M0 platform while improving protocol compatibility
4. Enabling protocol-specific administration of earning capabilities
### Key Features
* **DeFi Compatibility**: Works with lending platforms, AMMs, and other protocols that require standard ERC-20 behavior
* **Yield Preservation**: Maintains yield-earning capabilities of the underlying $M token
* **Value & Solvency**: wM aims for a direct value link to $M for earners. The target value for an earning token is its
underlying $M principal plus accrued yield:
```math
1 \text{ }\text{earning }\text{wM} + \text{wM yield } ≈ 1 \text{ }\text{M} + \text{Accrued Yield}
```
Critically, the entire system maintains a solvency invariant, ensuring it's always fully backed. The total $M held by
the wM contract is always greater than or equal to the total wM supply plus all accrued yield owed to earners:
```math
\text{M Balance}_{(\text{wM contract})} \ge \underbrace{\text{totalNonEarningSupply} + \text{projectedEarningSupply}}_{\text{Total wM Liabilities, aka wM supply + accrued yield}}
```
This guarantee is maintained through the system's design, where yield earned on $M held for non-earning wM holders,
along with minor positive rounding effects during transfers, accumulates as "excess" reserves within the contract.
* **Flexible Yield Options**: Configurable claiming and recipient mechanisms
* **Delegated Administration**: Trusted admins can manage earner status without governance
* **Protocol Value Capture**: System design naturally accumulates excess reserves that benefit the broader protocol
ecosystem
### Comparison to $M
| **Feature** | **$M** | **Wrapped M** |
| ------------------ | ---------------------------------- | --------------------------------------- |
| Balance Behavior | Automatically increases (rebasing) | Remains static until explicit claim |
| Earning Mechanism | Implicit (balances grow) | Explicit (yield accumulates separately) |
| Yield Realization | Automatic with balance updates | Manual claiming required |
| DeFi Compatibility | Limited due to rebasing | Wide compatibility with standard ERC-20 |
| Recipient Options | Limited | Configurable yield recipients |
| Administration | Governance only | Governance + Delegated admins |
## Integration considerations
### I. For DeFi Protocols
When integrating with wM, protocols should be aware of:
* Standard ERC-20 behavior (no rebasing)
* Distinction between balance and balance-with-yield
* Impact of earning status on user experience
* Potential for yield to accumulate while tokens are locked
### II. For Developers
Developers working with wM should understand:
* The relationship between wM and the underlying $M token
* The impact of earning status on token accounting
* The claiming process and its effect on balances
* The consistent rounding patterns used throughout the contract
### III. Common Patterns
Several patterns are recommended when working with wM:
* Check both `balanceOf()` and `balanceWithYieldOf()` to understand a user's position
* Consider calling `claimFor()` before critical operations to ensure balances reflect all value
* Be aware of the impact of earning status transitions on token amounts
* Understand the priority order for claim recipients when handling claimed yield
## Features and Operations
### I. Basic Token Operations
#### Wrapping $M to wM
Users can deposit $M tokens to receive an equivalent amount of wM:
```solidity
wrap(recipient, amount)
```
The $M tokens are held by the wM contract and can potentially earn yield.
#### Unwrapping wM to M
Users can burn wM tokens to receive the equivalent amount of M:
```solidity
unwrap(recipient, amount)
```
Any accrued yield remains in the user's wM balance if they're in earning mode.
#### Gasless Operations
The contract supports wrapping with permits for improved UX:
```solidity
wrapWithPermit(recipient, amount, deadline, signature)
```
#### Standard ERC-20 Operations
All standard ERC-20 functions are supported, including:
* `transfer(to, amount)`
* `transferFrom(from, to, amount)`
* `approve(spender, amount)`
* `balanceOf(account)`
### II. Earning Management
#### Starting to Earn
Approved accounts can activate earning status:
```solidity
startEarningFor(account)
```
This converts their balance to a principal amount that will begin accruing yield.
#### Stopping Earning
Accounts can deactivate earning status (or anyone can do this for an account that's no longer approved):
```solidity
stopEarningFor(account)
```
This claims any outstanding yield and converts the balance back to non-earning.
#### Global Earning Status
The wM contract itself must be an approved earner in the $M token system. Earning can be enabled or disabled for the
entire system:
```solidity
enableEarning()
disableEarning()
```
A key improvement in wM v2 is the ability to re-enable earning after it has been disabled.
### III. Yield Management
#### Claiming Yield
Accrued yield can be claimed at any time:
```solidity
claimFor(account)
```
This increases the account's wM balance by the yielded amount and triggers appropriate transfers based on recipient
settings.
#### Setting Claim Recipients
Users can direct their yield to another address:
```solidity
setClaimRecipient(recipient)
```
This allows for flexible yield strategies without changing the underlying balance.
#### Checking Accrued Yield
Several view functions allow users and integrations to check the yield that has accrued for an earning account but has
**not yet been claimed**:
* `accruedYieldOf(account)`:
* Returns the amount of wM tokens earned as yield based on the account's `earningPrincipal` and the time elapsed
(represented by the increase in the `currentIndex`) **since the last claim or since earning started**.
* This represents **only the unclaimed portion** of the yield. It does *not* include yield that has already been
claimed and added to the balance.
* If the account is not in Earning Mode, this returns 0.
* `balanceWithYieldOf(account)`:
* Returns the sum of the account's current stored `balanceOf` **plus** the currently claimable `accruedYieldOf`.
* This function effectively shows the total wM value attributable to the account *at that moment*, representing what
the balance *would be* if the accrued yield were claimed instantly (before considering any fees or alternate claim
recipients).
**Effect of Claiming:** When `claimFor(account)` is successfully executed, the calculated accrued yield is processed. If
the yield (net of fees) is directed to the account owner, their stored `balanceOf` increases. Consequently, immediately
after a claim, `accruedYieldOf` will return 0 (or a negligible amount due to block timing), as the yield is no longer
"accrued but unclaimed". `balanceWithYieldOf` will then equal `balanceOf`. New yield will start accumulating again as
time passes and the `currentIndex` increases further.
### IV. Excess Management & Value Capture
**Concept:** The wM contract holds underlying $M tokens. Because the wM contract itself earns yield on *all* these $M
tokens, but wM yield is only accrued for *earning-mode* wM holders, a surplus (or "excess") of $M tokens naturally
accumulates within the contract over time. This primarily comes from the $M yield earned on tokens backing non-earning
wM balances.
**Important note:**
\:::note M and WrappedM are sharing the same Earners list. An earner of $M is an earner of WrappedM :::
**Claiming Excess:** This accumulated excess M, representing value captured by the protocol, can be claimed and
transferred out using the `claimExcess()` function.
```solidity
claimExcess() returns (uint240 claimed_)
```
**Destination:** The excessDestination address, set during deployment, receives these claimed tokens. Typically, this is
the **Distribution Vault**, directing this value back to the protocol ecosystem and ultimately benefiting Zero token
holders.
### V. Admin Operations via EarnerManager
The EarnerManager contract empowers designated admins ('special users') with the delegated authority to approve wM
earners, managing status for potentially many accounts outside the standard governance process
#### Setting Earner Status
Admins can enable accounts for earning and set fee rates:
```
setEarnerDetails(account, status, feeRate)
```
#### Bulk Operations
Multiple accounts can be managed in a single transaction:
```
setEarnerDetails(accounts[], statuses[], feeRates[])
```
#### Checking Earner Status
Various functions provide information about earner status:
```
earnerStatusFor(account)
getEarnerDetails(account)
```
## Core Concepts
### I. Dual Mode: Earning vs. Non-Earning
The wM token operates in two distinct modes for each account:
#### A. Non-Earning Mode
* Default state for new accounts
* Balances remain static (standard ERC-20 behavior)
* No yield accrues on these tokens
* Simpler accounting with direct token amounts
#### B. Earning Mode
* Must be explicitly activated (requires approval via Governance or EarnerManager admin designation)
* Balances remain static in storage
* Represents a growing value based on a **principal amount** and an **index**, using an accounting model analogous to
the native $M token.
* Crucially, the **index used is derived from the underlying $M token** (wM does not maintain its own independent
index).
* Yield accumulates based on this principal and derived index, and can be claimed separately.
Users can switch between Non-Earning and Earning modes if they meet the requirements for earning.
### II. Account Status
Each wM account maintains several key properties tracked internally within the `WrappedMToken` contract:
* **Balance**: The current wM token balance held by the account. This value remains static between operations
and *excludes* any unclaimed yield.
* **Earning Status**: A boolean flag indicating whether the account is currently in Earning Mode (true) or Non-Earning
Mode (false).
* **Earning Principal**: For accounts in Earning Mode, this stores the base principal amount used for yield
calculations. This value is derived from the account's balance when it starts earning and remains constant while
earning.
* **Claim Recipient**: An optional address specified by the user via `setClaimRecipient()` to receive their claimed
yield. If not set by the user, and no Governance override exists, yield is sent to the account owner itself.
* **Earner Details**: A boolean flag indicating if settings apply when an account's earning status is managed by an
EarnerManager admin.
* If true, it implies the EarnerManager contract holds details (managing admin address, fee rate) for this account.
* When a fee rate is set by the admin, a portion of the claimed yield (calculated as yield \* feeRate / 10000) is sent
to that admin upon claiming, and the remaining net yield goes to the designated claim recipient (user-set,
governance-set, or default owner).
**Principal, Balance, and Yield Relationship:**
When an account enters Earning Mode (`startEarningFor`), its current balance is converted into its
initial `earningPrincipal`.
This conversion is performed by the `IndexingMath.getPrincipalAmountRoundedDown`(balance, currentIndex) function, which
calculates the principal amount corresponding to the balance at the currentIndex, taking into account the system's
fixed-point scaling for the index and applying rounding rules that slightly favor the protocol.
This `earningPrincipal` remains fixed while the account continues in Earning Mode. As the global `currentIndex` (derived
from the $M token's index) increases over time, the value represented by this fixed principal grows.
The fundamental relationship is:
```math
\text{Value Represented} = \text{earningPrincipal} \times \text{currentIndex}
```
The account's stored balance only increases when yield is explicitly claimed. Therefore, the `accruedYield` represents
the difference between the current value represented by the principal and the static stored balance:
```math
\text{accruedYield} = \text{earningPrincipal}\times\text{currentIndex}-\text{balance}
```
So, the target state before claiming is effectively:
```math
\text{balance} + \text{accruedYield}≈\text{earningPrincipal}\times\text{currentIndex}
```
Calling the `claimFor()` function calculates this `accruedYield`. It then handles potential fee deductions
if `hasEarnerDetails` is true (sending the fee portion to the managing admin). The remaining net yield is transferred to
the designated `claimRecipient`. If the `claimRecipient` is the account owner itself, their stored balance is increased
by this net yield amount. This operation "catches up" the account's stored balance to reflect the value earned up to
that point (minus any fees paid).
### III. Yield Generation and Claiming
#### How Yield Accrues
For accounts in earning mode, yield accrues based on:
* The account's principal amount
* The global wM index (derived from the $M token's index)
* The time elapsed since the last index update
The formula is:
```math
accruedYield=(principal\times currentIndex) - balance
```
Where:
* `principal` is the stored earning principal for the account
* `currentIndex` is the current wM index
* `balance` is the current token balance
#### Claiming Process
When yield is claimed:
1. The accrued yield is calculated based on the current index
2. The account's balance is provisionally increased by the full yield amount
3. If the account's earning status is managed by an active EarnerManager admin with a configured fee rate, a percentage
of the yield is deducted and sent to that admin as a fee
4. The remaining yield (minus any fees) is transferred to the designated recipient (or remains with the account owner if
they are the recipient)
Claiming doesn't change the principal amount, only the final balance reflects the net yield received
### IV. Index Mechanism
Rather than maintaining its own independent index, wM derives its index from the $M token's index:
#### When Earning is Enabled
The wM index is calculated as:
```math
wM_{index} = (M_{current index} \text{ }/ \text{ }enableMIndex) \times disableIndex
```
Where:
* `M current index` is the current index from the $M token
* `enableMIndex` is the $M index when earning was enabled
* `disableIndex` is the previous wM index (if earning was previously disabled)
#### When Earning is Disabled
The wM index becomes static:
```math
wM_{index} = disableIndex
```
This approach ensures that wM's yield calculation accurately reflects the underlying $M token's yield performance.
### V. Yield Recipients
The wM system provides flexible options for directing claimed yield:
#### Default Behavior
By default, yield is claimed to the same account that generated it.
#### Custom Recipients
Account holders can specify a different address to receive their yield by calling `setClaimRecipient()`.
#### Governance Overrides
The TTG governance can set override recipients for specific accounts through the Registrar.
#### Priority Order
When determining where to send claimed yield:
1. User-specified recipient (if set)
2. Governance-specified override (if set)
3. The account itself (default)
This flexibility allows for a variety of yield utilization strategies.
### VI. System Invariants and Excess
The wM system is designed around a critical solvency invariant to ensure it's always fully backed by the underlying $M
token.
**Balance Invariant**: The total $M token balance held by the `WrappedMToken` contract must always be greater than or
equal to the sum of all wM tokens in circulation plus all accrued yield owed to earners.
Expressed conceptually:
```math
\text{M Balance}_{(\text{wM contract})} \ge \text{Total wM Supply} + \text{Total Accrued Yield}
```
Expressed using contract variables:
```math
\text{M Balance}_{(\text{wM contract})} \ge \underbrace{\text{totalNonEarningSupply} + \text{projectedEarningSupply}}_{\text{Total wM Liabilities}}
```
**How the Invariant is Maintained:**
Crucially, the `M Balance` held by the `WrappedMToken` contract is not static. Since the `WrappedMToken` contract itself
is registered as an $M earner (as shown in the $M dashboard where the wM contract is the largest holder), **the
underlying $M tokens it holds continuously accrue yield according to the $M token's rebasing mechanism.**
This intrinsic yield generation on the *entire* pool of $M tokens held within the wM contract is fundamental to
maintaining the solvency invariant. It ensures the $M balance grows naturally over time, actively keeping assets (M
held) ahead of liabilities (wM supply + accrued yield).
**Excess Reserves:**
The difference between the actual `M Balance` held by the contract and the total calculated liabilities
(`totalNonEarningSupply + projectedEarningSupply`) constitutes the **"excess"** reserves within the system. This excess
primarily accumulates from two sources explained further below:
1. The yield generated by the underlying $M tokens that back the balances of **non-earning** wM holders (as this yield
isn't owed back to them).
2. Minor positive rounding effects during various token operations (transfers, wrapping/unwrapping, starting/stopping
earning).
This invariant and the resulting excess ensure that the system remains fully solvent, can always honor unwrap requests,
and captures additional value for the protocol ecosystem.
## Usage & Security
### Security Considerations
Key security aspects of the TTG system include:
* **Trusted Parties**: The model relies on the honest behavior of several actors:
* Validators are trusted for offchain data verification and intervention (cancel/freeze).
* Minters are trusted to manage offchain collateral responsibly.
* Governance Participants (`POWER`/`ZERO` holders and delegates) are trusted to vote in the protocol's best interest.
* **Governor Security & Access Control**:
* Modifying the `Registrar` is strictly limited to `StandardGovernor` and `EmergencyGovernor`.
* Proposal actions within governors are restricted to predefined internal functions targeting `self`, preventing
arbitrary code execution by proposals.
* Epoch-based locks on `POWER` token transfers/delegations during Voting Epochs are a key defense against
`StandardGovernor` vote manipulation.
* `ZeroGovernor` holds significant, concentrated power over the entire governance structure via the `resetTo*` and
threshold/`CashToken` setting functions.
* **Voting Thresholds**:
* `EmergencyGovernor` and `ZeroGovernor` employ high voting thresholds requiring substantial consensus (minimum
`_MIN_THRESHOLD_RATIO` of 2.71% enforced, typically set much higher like 65%), protecting against capture but
demanding significant coordination for action.
* `StandardGovernor` uses economic incentives (`POWER` inflation / `ZERO` rewards) rather than a fixed quorum
percentage to encourage participation, relying on rational actors voting to avoid dilution/missed rewards.
* **CashToken Safety**: The system relies on the assumption that allowed `CashToken`s (list controlled by
`ZeroGovernor`, initially set at deployment) are standard, well-behaved ERC20 tokens without malicious hooks (e.g.,
transfer fees, reentrancy vectors, rebasing). Using non-standard tokens could introduce vulnerabilities. WETH and $M
are the currently expected/intended CashTokens.
* **Reset Mechanism (`ZeroGovernor`)**: The ability for `ZeroGovernor` to redeploy core governance contracts
(`resetToPowerHolders`, `resetToZeroHolders`) is the ultimate control mechanism. It enables protocol upgrades and
recovery but represents a central point of authority vested in `ZERO` holders. Execution mid-epoch effectively cancels
ongoing proposals in the old governors.
* **Inflation/Reward Complexity**: The intricate interplay between `StandardGovernor` tracking voter participation per
epoch, `Power token` calculating and deferring inflation realization (`markParticipation`, `_sync`), and `Zero token`
receiving mint calls requires precise implementation. Bugs could disrupt core incentives.
* **Epoch-Based Timing**: The system relies heavily on the `PureEpochs` mechanism for proper functioning (voting
windows, transfer locks, auction timing). All participants must be aware of the current epoch status and restrictions.
* **Emergency Actions**: The `EmergencyGovernor` provides faster execution for urgent needs but requires a higher
consensus threshold, balancing responsiveness with security.
### Roles in Governance
* **Power token Holder**: Owns `POWER`. Benefits from `POWER` inflation if their voting power (direct or delegated)
fully participates in `StandardGovernor` voting epochs. Can vote directly or delegate. Can purchase discounted `POWER`
in auctions. Subject to dilution if inactive.
* **Power token Delegatee**: An address (can be the holder themselves) to which `POWER` voting power is delegated. Casts
votes in `StandardGovernor` and `EmergencyGovernor`. If they vote on all `StandardGovernor` proposals in an active
epoch, they trigger `POWER` inflation for the holder and receive `ZERO` token rewards directly to their own address.
* **Zero token Holder**: Owns `ZERO` . Can vote in `ZeroGovernor` or delegate. Can claim accumulated assets from the
`DistributionVault` pro-rata to their `ZERO` holdings in past epochs. Benefits indirectly from `POWER` auctions
(proceeds go to Vault).
* **Zero token Delegatee**: An address (can be the holder themselves) to which `ZERO` voting power is delegated. Casts
votes in `ZeroGovernor`. Does *not* directly receive `ZERO` rewards from `StandardGovernor` voting (those go to the
`POWER` Delegatee).
* **Proposer**: Any address can create proposals in any governor. Must pay a fee in `CashToken` for `StandardGovernor`
proposals (refunded on success). No fees for `EmergencyGovernor` or `ZeroGovernor`.
* **Executor**: Any address can call `execute()` on a Governor for a passed proposal (`Succeeded` state) during its
valid execution window.
* **Distributor (Vault)**: Any address can (and should periodically) call `distribute(token)` on the `DistributionVault`
to ensure new funds are accounted for and become claimable by `ZERO` holders for the relevant epoch.
### Interacting with TTG
* **Voting**: Use `castVote`, `castVotes`, or their `WithReason` / `BySig` variants on the appropriate Governor
(`StandardGovernor`, `EmergencyGovernor`, `ZeroGovernor`). Check the proposal `state()` and be mindful of epoch
restrictions (esp. `StandardGovernor` voting *only* in Voting Epochs).
* **Delegation**: Use `delegate` or `delegateBySig` on `Power token` (only during Transfer Epochs) or `Zero token`
(anytime). Remember `ZERO` rewards go to the `POWER` delegatee.
* **Checking `POWER` Balance**: **Crucially**, always call `Power token.sync(account)` *before* relying on
`Power token.balanceOf(account)` if you expect the balance might have changed due to inflation. This function realizes
any pending inflation into the actual balance. Use `getPastVotes` for historical voting power checks (which inherently
accounts for inflation up to that point).
* **Claiming Vault Rewards**: Call `DistributionVault.claim(...)` specifying the token and a *past* epoch range
(`startEpoch`, `endEpoch`). Use `getClaimable(...)` to check expected amounts first. Ensure `distribute(token)` has
been called recently for the desired token to make funds available for the latest completed epochs.
* **Power token Auctions**: Call `Power token.buy(...)` during Transfer Epochs. Check `amountToAuction()` and
`getCost(amount)` beforehand. Ensure your account has sufficient `CashToken` allowance approved to the `Power token`
contract.
### Conclusion
M0's Two-Token Governance system offers a sophisticated and resilient framework for decentralized decision-making. By
carefully separating powers across three governor tiers, leveraging a deterministic epoch-based clock, and implementing
robust incentive mechanisms via `Power token` inflation and `Zero token` rewards/revenue sharing, TTG encourages active
participation while maintaining security and adaptability.
The key components work in concert:
1. `Power token` and `Zero token` provide distinct spheres of influence: operational vs. meta-governance.
2. `StandardGovernor`, `EmergencyGovernor`, and `ZeroGovernor` offer tailored processes for different decision types.
3. The `Registrar` ensures a single, secure source of truth for parameters.
4. The `DistributionVault` aligns economic incentives for `Zero token` holders.
5. The Epoch System creates predictable cycles and enhances security.
The intricate architecture of TTG provides both the flexibility needed for routine adjustments and the security required
for long-term protocol stability and evolution. Understanding the specific roles, rules, and interactions of the tokens
and contracts is key to effective participation.
## Token Mechanics: POWER & ZERO
### Power token (`PowerToken.sol`)
*With great power comes great responsibility.*
The primary token for operational governance, featuring strong incentives for active participation.
* **Standard & Properties**: ERC-20 compatible. Implements `IEpochBasedInflationaryVoteToken` which includes:
* Epoch-based snapshots for historical balances/votes.
* ERC-5805 for voting and delegation (`delegate`, `getPastVotes`).
* ERC-3009 for transfer authorization (`transferWithAuthorization`).
* **0 decimals**.
* **Voting Usage**: The voting token for `StandardGovernor` (simple majority) and `EmergencyGovernor` (threshold-based). Vote weight is determined by the balance snapshot taken at the *end* of the epoch *before* the voting epoch (`proposalSnapshot` function returns `voteStart - 1`).
* **Inflationary Mechanism (\~10% target per active voting epoch)**:
* **Activation**: An epoch becomes "active" for inflation calculation when the *first* `StandardGovernor` proposal targeting that epoch is created. This proposal action calls `Power token.markNextVotingEpochAsActive()`, which pre-calculates the target total supply for the end of that voting epoch based on a \~10% increase (`participationInflation` parameter in `Power token`).
* **Distribution Trigger**: At the end of a Voting Epoch, the `StandardGovernor` identifies voting power (direct or delegated via `voter_` address in `_castVote`) that participated in *every single proposal* during that epoch. For each fully participating delegatee address, it calls `Power token.markParticipation(delegatee)`.
* **Distribution Execution**: Inside `Power token`, `_markParticipation` calculates the inflation amount based on the delegatee's *current* voting power (`_getVotes`). This amount is immediately added to the delegatee's tracked voting power (`_votingPowers`) and the overall token supply (`_totalSupplies`). Crucially, the actual *token balance* (`_balances`) increase for the underlying holders is *deferred* until their next interaction with the token (e.g., `transfer`, `delegate`, `sync`) which triggers the internal `_sync` function to realize the inflation.
* **Incentive Rationale**: This system directly rewards *consistent participation* in standard governance. Missing even one vote means forfeiting the \~10% inflation for that epoch, resulting in dilution relative to fully active participants. Holders whose delegates participate fully benefit from this inflation.
* **Dutch Auction for Unallocated Inflation**:
* **Source**: The difference between the target supply (calculated at the start of the Voting Epoch) and the actual total supply after inflation distribution. This gap arises primarily from voting power that didn't fully participate. The `amountToAuction()` function calculates this difference.
* **Timing**: Auctions are active *only* during Transfer Epochs.
* **Price Mechanism**: Uses a Dutch auction format (`getCost()` calculates the price in `CashToken` per `POWER`). The 15-day epoch is divided into 100 periods (`_AUCTION_PERIODS`). The price starts high and decreases linearly *within* each period. The *starting* price for each period is half the starting price of the preceding period, creating an overall exponential decay curve. Pricing is relative to the previous epoch's total supply to ensure consistent price dynamics over time.
* **Purchase**: Users call `buy()` providing the active `CashToken` (e.g., WETH) and specifying amounts and expiry. Requires `CashToken` allowance.
* **Proceeds Destination**: The collected `CashToken` is transferred directly to the `DistributionVault`.
* **Transfer & Delegation Restrictions**: Transfers (`transfer`, `transferFrom`) and delegation (`delegate`) are *only permitted during Transfer Epochs*, enforced by the `notDuringVoteEpoch` modifier. These actions also trigger `_sync()` to update the account's balance with any pending inflation.
* **Bootstrapping**: The `constructor` or a `ZeroGovernor` reset (`resetToPowerHolders`, `resetToZeroHolders`) initializes the `Power token` state. It distributes an `INITIAL_SUPPLY` of 1,000,000 tokens proportionally based on the balances of a specified `bootstrapToken` (like `PowerBootstrapToken.sol`, a previous `Power token` version, or the `Zero token`) at the `bootstrapEpoch` (the epoch before deployment/reset). Initial delegatee is self.
### Zero token (`ZeroToken.sol`)
*It all starts at zero.*
The apex token for meta-governance and claiming protocol-generated revenue.
* **Standard & Properties**:\
ERC-20 compatible. Implements `IEpochBasedVoteToken` which includes:
* Epoch-based snapshots for historical balances/votes.
* ERC-5805 for voting and delegation (`delegate`, `getPastVotes`, `pastBalancesOf`, `pastTotalSupplies`).
* ERC-3009 for transfer authorization (`transferWithAuthorization`).
* **6 decimals**.
* **Supply Dynamics**:\
Generally static. The total supply increases *only* when the `StandardGovernor` explicitly mints new `ZERO` tokens as rewards for governance participation. The initial supply (e.g., 1,000,000,000) is determined at deployment via the `constructor` minting to initial accounts.
* **Minting Mechanism (Rewards)**:
* Minting is controlled exclusively by the active `StandardGovernor` contract, enforced by the `only`StandardGovernor\`\` modifier on the `Zero token.mint()` function.
* Triggered when \`\`StandardGovernor`._castVote` processes the *final* vote for a voter (`voter_`) in an active Voting Epoch (i.e., they voted on all proposals).
* The reward is minted to the `voter_` address provided in the \`\`StandardGovernor`._castVote` context, which corresponds to the address whose voting power was utilized (this is typically the *delegatee's* address if power was delegated). This incentivizes becoming an active delegate.
* The reward amount is calculated proportionally to the `POWER` voting power used (`weight_`), but capped at a maximum total of 5 million `ZERO` per epoch (`maxTotalZeroRewardPerActiveEpoch` in `StandardGovernor`). Formula: `reward = (maxTotalZeroRewardPerActiveEpoch * weight_) / totalSupply(epoch - 1)`.
* **Voting Usage**:\
The voting token for the `ZeroGovernor` (threshold-based). Vote weight is determined by the `ZERO` balance snapshot taken at the *end* of the epoch *before* the voting epoch (`proposalSnapshot`).
* **Core Utility**:
* Grants voting rights on fundamental meta-governance proposals within the `ZeroGovernor` (system resets, `CashToken` changes, threshold adjustments).
* Entitles holders to claim a pro-rata share of various assets accumulated in the `DistributionVault`. Claiming is based on `ZERO` holdings in *past* epochs.
* Provides an indirect economic benefit from `Power token` auctions, as the proceeds (`CashToken`) flow into the `DistributionVault`.
import ZoomableImage from '@/components/ZoomableImage';
## Two Token Governance (TTG): Overview
*Coordinating the protocol's evolution.*
### Introduction
Two Token Governance (TTG) is the robust, multi-layered governance system powering the M0 Protocol. Inspired by
constitutional systems with checks and balances, it provides a balanced approach to protocol management through a
carefully designed dual-token framework that separates day-to-day operational decisions from foundational
meta-governance authority.
TTG divides governance responsibilities between two primary tokens:
* **Power token (`POWER`)**: The primary token for operational governance proposals and emergency actions, utilized
within the `StandardGovernor` and `EmergencyGovernor`.
* **Zero token (`ZERO`)**: The meta-governance token used within the `ZeroGovernor`, controlling the governance
framework itself and enabling holders to claim a share of protocol revenue via the `DistributionVault`.
This segregation of powers, combined with a deterministic epoch-based system and sophisticated incentive mechanisms,
creates a resilient and adaptable governance structure designed for long-term protocol health, security, and
decentralized control.
### Core Principles
* **Separation of Powers**: Distinct tokens (`POWER`, `ZERO` ) and governor contracts (`StandardGovernor`,
`EmergencyGovernor`, `ZeroGovernor`) handle different scopes of decisions, ensuring appropriate oversight for routine
changes versus fundamental system modifications.
* **Incentivized Participation**: `Power token`'s inflationary mechanism rewards active voting in standard governance,
while `Zero token` ownership grants access to protocol revenue via the `DistributionVault`, encouraging informed
engagement.
* **Structured Process**: Governance actions adhere to fixed 15-day epoch cycles (`Transfer` and `Voting` epochs),
defined proposal types per governor, and clear voting rules (majority vs. threshold), enhancing security and
predictability.
* **Centralized Configuration**: The `Registrar` contract acts as the single source of truth for all protocol parameters
(rates, lists, intervals, etc.), modifiable only through successful, formally passed governance proposals.
### Core Components Overview
The TTG system comprises several interconnected smart contracts:
Picture representing the TTG Smart Contract Architecture diagram:
#### Governance Components
* **`StandardGovernor` (\`\`StandardGovernor`.sol`)**: Manages regular governance for common operations using
`Power token`. Uses simple majority voting (yes > no) and distributes rewards (`ZERO` tokens and enabling `POWER`
inflation) for full participation in active voting epochs. Requires a proposal fee.
* **`EmergencyGovernor` (\`\`EmergencyGovernor`.sol`)**: Handles emergency governance for critical situations using
`Power token`. Uses threshold-based voting (minimum percentage of total `POWER` supply) for faster decisions. No
proposal fee or direct incentives.
* **`ZeroGovernor` (\`\`ZeroGovernor`.sol`)**: Manages meta-governance for system resets and fundamental protocol changes
using `ZERO` . Controls the governance framework itself through threshold-based voting. No proposal fee.
* **Registrar (`Registrar.sol`)**: Central parameter store containing all protocol configurations (lists like
`APPROVED_MINTERS`, key-value pairs like `MINT_RATE_MODEL`). Single source of truth, writable only by
`StandardGovernor` and `EmergencyGovernor`.
* **DistributionVault (`DistributionVault.sol`)**: Collects protocol revenue (e.g., `Power token` auction proceeds,
unrefunded proposal fees, excess yield) and distributes various assets pro-rata to `Zero token` holders based on past
epoch holdings.
#### Token Components
* **Power token (`Power token.sol`)**: ERC-20 compatible voting token with **0 decimals**. Features epoch-based
mechanics, inflation rewards (\~10% target per active voting epoch for full participants), delegation (ERC-5805),
transfer authorization (ERC-3009), and Dutch auctions for unallocated inflation. Used in `StandardGovernor` and
`EmergencyGovernor`.
* **Zero token (`Zero token.sol`)**: ERC-20 compatible meta-governance token with **6 decimals**. Features epoch-based
mechanics, delegation (ERC-5805), and transfer authorization (ERC-3009). Supply primarily increases through rewards
minted by `StandardGovernor` for governance participation. Entitles holders to claim revenue from `DistributionVault`.
Used in `ZeroGovernor`.
#### Supporting Contracts
* **Deployers**: Specialized contracts (`Power tokenDeployer.sol`, `StandardGovernorDeployer.sol`,
`EmergencyGovernorDeployer.sol`) controlled by `ZeroGovernor` that deterministically deploy new instances of core
governance contracts during system resets using `CREATE`.
* **PureEpochs (`PureEpochs.sol`)**: Library defining the epoch-based time system (fixed 15-day periods) that structures
all governance activities.
#### Configs
Current governance and protocol configurations can be found at
[governance.m0.org/config/governance](https://governance.m0.org/config/governance) and
[governance.m0.org/config/protocol](https://governance.m0.org/config/protocol) respectively.
## Governance Structure Tiers
TTG employs a three-tiered approach to decision-making:
### 1. Standard Governance (via `StandardGovernor` - `POWER` holders)
The primary governance layer for routine protocol management.
* **Purpose**: Routine adjustments and list management essential for day-to-day protocol operation.
* **Scope**: Modifying parameters stored in the `Registrar` like interest rates, time intervals, mint ratios; adding/removing addresses from lists (`APPROVED_MINTERS`, `APPROVED_VALIDATORS`, `APPROVED_EARNERS`). Also controls its own proposal fee.
* **Voting**: Uses `POWER`. Simple majority (Yes votes > No votes) of participating tokens. Occurs *only* during Voting Epochs. No fixed quorum percentage.
* **Proposal Types**: Restricted to calling specific functions on `StandardGovernor` itself, which then interact with the `Registrar` or internal settings:
* `addToList(bytes32 list, address account)`
* `removeFromList(bytes32 list, address account)`
* `removeFromAndAddToList(bytes32 list, address accountToRemove, address accountToAdd)`
* `setKey(bytes32 key, bytes32 value)`
* `setProposalFee(uint256 newProposalFee)`
* **Incentives**: Drives `Power token` inflation and `Zero token` rewards for *full participation* within an active Voting Epoch. Requires a `proposalFee` paid in the active `CashToken`.
* **Lifecycle**: Propose (during Transfer Epoch N) → Vote (during Voting Epoch N+1) → Execute (during Transfer Epoch N+2). Expired if not executed in time.
### 2. Emergency Governance (via `EmergencyGovernor` - `POWER` holders)
A specialized governance layer for urgent protocol changes, sometimes referred to as the Priority Governor.
* **Purpose**: Addressing urgent issues or critical parameter changes that cannot wait for the standard cycle.
* **Scope**: Similar parameter and list changes via the `Registrar` as `StandardGovernor`, plus the ability to set the `StandardGovernor`'s proposal fee.
* **Voting**: Uses `POWER`. High threshold (e.g., 65% of *total possible* `POWER` votes at snapshot required). Voting spans the proposal epoch and the subsequent one (`voteEnd = voteStart + 1`).
* **Proposal Types**: Restricted to calling specific functions on `EmergencyGovernor` itself:
* `addToList(bytes32 list, address account)`
* `removeFromList(bytes32 list, address account)`
* `removeFromAndAddToList(bytes32 list, address accountToRemove, address accountToAdd)`
* `setKey(bytes32 key, bytes32 value)`
* `setStandardProposalFee(uint256 newProposalFee)`
* **Incentives**: No direct token incentives (no `POWER` inflation or `ZERO` rewards triggered). No proposal fee required. Designed for exceptional circumstances.
* **Lifecycle**: Propose (any epoch N) → Vote (during Epoch N and N+1) → Execute (anytime during Epoch N or N+1 as soon as threshold is met). Expired if not executed by end of Epoch N+1.
### 3. Meta-Governance (via `ZeroGovernor` - `ZERO` holders)
The highest governance layer with authority over the entire governance system structure.
* **Purpose**: Governing the governance system itself and fundamental protocol settings. Enables protocol upgrades and recovery.
* **Scope**: Initiating governance contract resets/upgrades, changing the allowed `CashToken`s, adjusting `EmergencyGovernor` and `ZeroGovernor` voting thresholds.
* **Voting**: Uses `ZERO` . Threshold-based (e.g., 65% of total `ZERO` supply at snapshot required). Voting spans the proposal epoch and the subsequent one (`voteEnd = voteStart + 1`).
* **Proposal Types**: Restricted to calling specific functions on `ZeroGovernor` itself:
* `resetToPowerHolders()`: Redeploys `Power token`, `StandardGovernor`, `EmergencyGovernor`, bootstrapping new `Power token` from *old* `Power token` balances.
* `resetToZeroHolders()`: Redeploys same contracts, bootstrapping new `Power token` from *current* `Zero token` balances.
* `setCashToken(address newCashToken, uint256 newProposalFee)`: Changes the designated `CashToken` (must be pre-approved) and sets the `StandardGovernor` fee.
* `setEmergencyProposalThresholdRatio(uint16 newThresholdRatio)`
* `setZeroProposalThresholdRatio(uint16 newThresholdRatio)`
* **Incentives**: Control over the system's future; access to protocol revenue via `DistributionVault`. No proposal fee required.
* **Lifecycle**: Propose (any epoch N) → Vote (during Epoch N and N+1) → Execute (anytime during Epoch N or N+1 as soon as threshold is met). Expired if not executed by end of Epoch N+1.
import ZoomableImage from '@/components/ZoomableImage';
## Core Contracts & Epoch System Deep Dive
### Component Deep Dive
#### Registrar (`Registrar.sol`)
The authoritative, onchain configuration registry for the M0 protocol. It acts as the single source of truth for
parameters.
* **Functionality**: Implements `IRegistrar`. Acts as a secure key-value store (`setKey(key, value)`, `get(key)`) and
manages critical address lists (`addToList(list, account)`, `removeFromList(list, account)`,
`listContains(list, account)`).
* **Control**: Write access (`setKey`, `addToList`, `removeFromList`) is strictly limited to the currently active
`StandardGovernor` and `EmergencyGovernor` contracts, enforced by the `onlyStandardOr`EmergencyGovernor\`\` modifier.
Read access is public.
* **Usage**: Essential for protocol operation. Core contracts (like those managing MToken minting/burning, rates) read
configurations from the Registrar. `listContains` is fundamental for permission checks (e.g., verifying if an address
is an approved Minter).
* **Key Parameters Stored (Examples)**:
| Parameter Key String (Used to derive `bytes32`) | Description | Modified By | Read By (Examples) |
| ----------------------------------------------- | ---------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------- |
| `"minters"` | Whitelist of accounts allowed to mint $M | StandardGov/EmergencyGov (via lists) | MinterGateway |
| `"validators"` | Whitelist of offchain collateral validators | StandardGov/EmergencyGov (via lists) | MinterGateway |
| **`"earners"`** | Whitelist for enabling $M earning status | StandardGov/EmergencyGov (via lists) | MToken, **WrappedMToken (direct & via EarnerMgr)** |
| **`"earners_list_ignored"`** | If set (non-zero), ignores the "earners" list | StandardGov/EmergencyGov (via `setKey`) | MToken, **WrappedMToken (direct & via EarnerMgr)** |
| `"minter_rate_model"` | Address of contract calculating minter interest | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"earner_rate_model"` | Address of contract calculating earner interest | StandardGov/EmergencyGov (via `setKey`) | MToken |
| N/A (Read via `vault()` func / constructor) | Address of the `DistributionVault` | `ZeroGovernor` (during deployment/reset) | MinterGateway, Power token, Registrar |
| `"mint_ratio"` | Maximum permitted mint-to-collateral ratio | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"update_collateral_interval"` | Required frequency for collateral updates | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"minter_freeze_time"` | Duration a minter is frozen upon penalty | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"penalty_rate"` | Penalty rate for infractions (e.g., missed updates) | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"update_collateral_threshold"` | Number of validator signatures needed | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"mint_delay"` | Delay before a mint proposal is executable | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"mint_ttl"` | Time-to-live for a mint proposal after delay | StandardGov/EmergencyGov (via `setKey`) | MinterGateway |
| `"base_minter_rate"` | Base rate for minter interest | StandardGov/EmergencyGov (via `setKey`) | MinterRateModel |
| `"max_earner_rate"` | Max rate for earner interest | StandardGov/EmergencyGov (via `setKey`) | EarnerRateModel |
| `"em_admins"` | List of admins allowed to manage earners via EarnerManager | StandardGov/EmergencyGov (via lists) | EarnerManager |
#### DistributionVault (`DistributionVault.sol`)
Aggregates and distributes various protocol revenues to `Zero token` holders.
* **Revenue Sources**: Designed to receive arbitrary ERC20 tokens. Key expected sources include:
* Excess yield generated by eligible collateral (must be transferred externally to the Vault's address).
* `CashToken` proceeds from `Power token` Dutch auctions (transferred automatically by `Power token.buy`).
* Unrefunded `CashToken` proposal fees from failed/expired `StandardGovernor` proposals (transferred by
\`\`StandardGovernor`.sendProposalFeeToVault`).
* **Distribution Accounting (Critical Process)**:
1. **Trigger**: The `distribute(token)` function *must be called* (permissionlessly, by any external account) for the
Vault to recognize newly received balances of a specific `token`.
2. **Calculation**: It measures the increase in the Vault's balance of `token` since the last call
(`IERC20(token).balanceOf(address(this)) - _lastTokenBalances[token]`).
3. **Recording**: This difference (the newly distributable amount) is added to the mapping
`distributionOfAt[token][currentEpoch]`, earmarking it for `ZERO` holders of the current epoch.
4. **State Update**: `_lastTokenBalances[token]` is updated to the current balance.
* **Claiming Mechanism**:
* `Zero token` holders call `claim(token, startEpoch, endEpoch, destination)` or its gasless signature variant
`claimBySig(...)`.
* The function processes epochs *strictly in the past* (`endEpoch < currentEpoch`). It reverts if the range includes
the current epoch or future epochs.
* It iterates from `startEpoch` to `endEpoch`. For each epoch `i`:
* It retrieves the claimant's historical `ZERO` balance (`claimantBalance`) and the total `ZERO` supply
(`epochTotalSupply`) for epoch `i` using `IZero token(zeroToken).pastBalancesOf(account, i, i)` and
`IZero token(zeroToken).pastTotalSupplies(i, i)`.
* It calculates the pro-rata share for that epoch:
`share = (distributionOfAt[token][i] * claimantBalance * _GRANULARITY) / epochTotalSupply`. The `_GRANULARITY`
constant (1e9) is used during accumulation to minimize precision loss from integer division.
* It checks the `hasClaimed[token][i][account]` mapping and skips the epoch if already claimed.
* After iterating, it marks all processed epochs (`startEpoch` to `endEpoch`) as claimed in the `hasClaimed` mapping
for that `token` and `account`.
* The final accumulated `claimed_` amount (scaled by `_GRANULARITY`) is divided by `_GRANULARITY` to get the true
token amount, `_lastTokenBalances[token]` is decremented, and the tokens are transferred to the `destination`.
* **State**: Maintains `_lastTokenBalances` (internal balance tracking), `distributionOfAt` (per-token, per-epoch
distributable amounts), and `hasClaimed` (per-token, per-epoch, per-user claim status) mappings. More about the
overall place of the Distribution Vault in the protocol in its
[dedicated section](/home/technical-documentations/distribution-vault/)
#### StandardGovernor (`StandardGovernor.sol`)
Manages the regular, day-to-day governance process with economic incentives.
* **Allowed Proposal Actions**: Limited to calling specific functions *on itself* which then interact with the
`Registrar` or its own settings: `addToList`, `removeFromList`, `removeFromAndAddToList`, `setKey`, and
`setProposalFee`. This prevents proposals from executing arbitrary code. Enforced by `onlySelf` modifier and internal
`_revertIfInvalidCalldata` check.
* **Proposal Fees**: Requires a `proposalFee` (set via governance, stored in `proposalFee` state variable, initially set
in constructor) paid in the currently active `CashToken` (`cashToken` state variable). The fee is transferred from the
proposer using `transferFrom` when `propose` is called. It is refunded via `transfer` upon successful execution
(`execute`), or can be optionally sent to the `DistributionVault` via `sendProposalFeeToVault` if the proposal is
Defeated or Expired. The `cashToken` and `proposalFee` can be changed by the `ZeroGovernor` via `setCashToken`.
* **Voting Mechanism**: Uses `POWER` tokens. Success requires a simple majority (Yes votes > No votes), checked within
the `state()` function logic (specifically `proposal_.yesWeight > proposal_.noWeight` when epoch > voteEnd). Voting is
strictly confined to the designated Voting Epoch (odd-numbered epoch N+1 following the proposal epoch N).
* **Proposal Lifecycle**:
1. **Propose**: During a Transfer Epoch (N). Pays fee. Records proposal details. Increments
`numberOfProposalsAt[N+1]`. If it's the first proposal for epoch N+1, triggers
`Power token.markNextVotingEpochAsActive`.
2. **Vote**: During the subsequent Voting Epoch (N+1). `state()` is `Active`. `POWER` transfers/delegations disabled.
Voters call `castVote` or `castVotes`.
3. **Tally & Outcome**: At the start of Epoch N+2 (Transfer Epoch), the outcome is determined based on votes tallied
at the end of Epoch N+1. `state()` becomes `Succeeded` or `Defeated`.
4. **Execute**: If `Succeeded`, can be executed by anyone calling `execute()` during Epoch N+2. Executes the proposed
action (e.g., `setKey` on `Registrar`). Refunds fee to proposer.
5. **Expire/Send Fee**: If `Defeated`, or `Succeeded` but not executed during Epoch N+2, becomes `Expired`. Fee can
then be sent to `DistributionVault` via `sendProposalFeeToVault`.
* **Reward Distribution**: Tracks participation per voter per epoch (`numberOfProposalsVotedOnAt`,
`numberOfProposalsAt`). If `hasVotedOnAllProposals` returns true for a voter (`voter_`) after they cast their final
vote in an epoch (checked inside `_castVote`), the contract calls `Power token.markParticipation(voter_)` (to enable
`POWER` inflation for the delegatee) and `Zero token.mint(voter_, reward)` (to distribute `ZERO` reward to the
delegatee). The `reward` calculation uses `maxTotalZeroRewardPerActiveEpoch`.
#### EmergencyGovernor (`EmergencyGovernor.sol`)
Provides an expedited path for critical governance actions using threshold consensus.
* **Allowed Proposal Actions**: Similar restricted scope as `StandardGovernor`, targeting itself to interact with the
`Registrar` or `StandardGovernor`: `addToList`, `removeFromList`, `removeFromAndAddToList`, `setKey`,
`setStandardProposalFee`. Enforced by `onlySelf` and `_revertIfInvalidCalldata`.
* **Proposal Fees**: None required.
* **Voting Mechanism**: Uses `POWER` tokens. Requires a high threshold (`thresholdRatio`, e.g., 6500 basis points = 65%)
of the *total* `POWER` supply at the snapshot block (`voteStart - 1`). The required number of votes is calculated by
`proposalQuorum()`. Success determined in `state()` by checking `yesWeight >= quorum()`. Faster voting window:
`voteEnd = voteStart + 1`, meaning voting spans the proposal epoch and the subsequent one.
* **Proposal Lifecycle**:
1. **Propose**: Can be proposed in any epoch (N). Records proposal details including the current `thresholdRatio`.
2. **Vote**: Voting is active during Epoch N and Epoch N+1. Voters call `castVote` or `castVotes`.
3. **Execute**: Can be executed by anyone calling `execute()` as soon *as* the state becomes `Succeeded` (i.e.,
threshold is met), up until the end of Epoch N+1. `proposalDeadline` is end of Epoch N+1. Executes the proposed
action.
4. **Outcome**: If not executed by end of Epoch N+1, becomes `Expired`. If threshold never met, remains `Defeated`.
* **No Direct Incentives**: Does not trigger `Power token` inflation or `Zero token` reward minting. Its use is reserved
for genuine emergencies. The threshold ratio can be changed by the `ZeroGovernor`.
#### ZeroGovernor (`ZeroGovernor.sol`)
The apex governor, controlling the structure of the governance system itself via threshold consensus.
* **Allowed Proposal Actions**: Limited to highly impactful, predefined functions targeting itself or the governors it
controls:
* `resetToPowerHolders()`: Redeploys `Power token`, `StandardGovernor`, `EmergencyGovernor` via their respective
Deployer contracts, bootstrapping the new `Power token` from the *old* `Power token` balances.
* `resetToZeroHolders()`: Redeploys the same contracts, but bootstraps the new `Power token` from the *current*
`Zero token` balances.
* `setCashToken(address newCashToken, uint256 newProposalFee)`: Changes the designated `CashToken` used for
`StandardGovernor` fees and `Power token` auctions (must be pre-approved in `_allowedCashTokens` mapping, set at
deployment). Also sets the `StandardGovernor` fee simultaneously via an internal call.
* `setEmergencyProposalThresholdRatio(uint16 newThresholdRatio)`: Adjusts the voting threshold for the
`EmergencyGovernor` via an internal call.
* `setZeroProposalThresholdRatio(uint16 newThresholdRatio)`: Adjusts the voting threshold for the `ZeroGovernor`
itself via `_setThresholdRatio`.
* **Proposal Fees**: None required.
* **Voting Mechanism**: Uses `ZERO` tokens. Requires a threshold (`thresholdRatio`) percentage of the total `ZERO`
supply at the snapshot block (`voteStart - 1`). `proposalQuorum()` calculates required votes. Success determined in
`state()` by checking `yesWeight >= quorum()`. Faster voting window: `voteEnd = voteStart + 1`.
* **Proposal Lifecycle**: Same faster lifecycle as `EmergencyGovernor`: Propose (N) -> Vote (N & N+1) -> Execute
(anytime during N or N+1, once threshold met). `proposalDeadline` is end of Epoch N+1.
* **Reset Functionality**: The `resetTo*` functions call the `deploy()` method on the relevant deployer contracts
(`Power tokenDeployer`, `StandardGovernorDeployer`, `EmergencyGovernorDeployer`). These deployers use `CREATE` to
deterministically deploy new contract instances. The `ZeroGovernor` emits a `ResetExecuted` event logging the
addresses of the newly deployed contracts and the bootstrap token used. This is the designated mechanism for major
governance upgrades or system recovery.
### Epoch System (`PureEpochs.sol`)
#### Overview
The entire governance system operates on a strict, time-based **epoch system** defined in the `PureEpochs.sol` library.
This structures governance activities and enhances security by preventing certain actions during sensitive periods.
* **Epoch Length**: Fixed at 15 days (`EPOCH_PERIOD = 15 days`).
* **Starting Timestamp**: Epoch 1 commenced on September 15, 2022, 06:42:42 AM UTC
(`STARTING_TIMESTAMP = 1_663_224_162`). The current epoch can be calculated based on `block.timestamp`.
* **Clock Mode**: Conforms to ERC-6372 standard, defined by `PureEpochs.clockMode()` (which encodes the starting
timestamp and period).
* **Epoch Types**: Epochs alternate between two types:
* **Transfer Epochs (Even-numbered Epochs)**:
* **Activity**: `Power token`s *can* be transferred and delegated. `Power token` Dutch auctions *are active* (if
supply available). `StandardGovernor` proposals *can* be created targeting the next Voting Epoch.
* **Voting Epochs (Odd-numbered Epochs)**:
* **Restrictions**: `Power token` transfers and delegations *are disabled* by the `notDuringVoteEpoch` modifier in
`Power token` to ensure stable voting power for the duration. `Power token` Dutch auctions *are inactive*.
* **Activity**: `StandardGovernor` proposals created in the previous Transfer Epoch *can be voted on*. `Power token`
inflation and `Zero token` rewards *are calculated and distributed* by the respective contracts based on voting
participation within *this* specific epoch.
* **Note on Emergency/Zero Governors**: Their faster proposal lifecycle (`voteEnd = voteStart + 1`) means voting can
span across the boundary between any two consecutive epochs (one Transfer, one Voting, or vice-versa), regardless of
type restrictions on `Power token` transfers. The system uses simple functions in `PureEpochs.sol` like
`currentEpoch()` and `timeRemainingInCurrentEpoch()` to manage time. This alternating Transfer/Voting epoch
structure is crucial for the integrity of `StandardGovernor` voting, preventing vote manipulation through
last-minute `Power token` movements.
#### Epoch-Based Token Tracking
Both `Power token` and `Zero token` inherit from `EpochBasedVoteToken`, which uses a sophisticated snapshot system to
track balances, total supply, voting power, and delegations historically across epochs. Key structures include:
```solidity
// Stores an amount valid from a specific epoch onwards
struct AmountSnap {
uint16 startingEpoch;
uint240 amount;
}
// Stores an address valid from a specific epoch onwards
struct AccountSnap {
uint16 startingEpoch;
address account;
}
// Storage patterns (simplified representation)
mapping(address => AmountSnap[]) internal _balances; // Balance snaps per account
mapping(address => AccountSnap[]) internal _delegatees; // Delegatee snaps per delegator
mapping(address => AmountSnap[]) internal _votingPowers; // Voting power snaps per delegatee
AmountSnap[] internal _totalSupplies; // Total supply snaps
```
This allows the system to efficiently query any past state using functions like `getPastVotes`, `pastBalanceOf`, and
`pastTotalSupplies`, which are essential for:
1. Determining voting weight for proposals based on past snapshots.
2. Calculating fair distribution in the `DistributionVault`.
3. Enabling the inflation (`_getUnrealizedInflation`) and auction (`_getTotalSupply(epoch - 1)`) mechanisms in
`Power token`.
4. Calculating `ZERO` rewards in `StandardGovernor` (`_getTotalSupply(epoch - 1)`).
#### Vote Batching
All three governors (`StandardGovernor`, `EmergencyGovernor`, `ZeroGovernor`) inherit from `BatchGovernor` and support
vote batching to enable efficient multi-proposal voting in a single transaction:
```solidity
// Cast votes for multiple proposals
function castVotes(
uint256[] calldata proposalIds,
uint8[] calldata supportList
) external returns (uint256 weight);
// Cast votes with reasons for multiple proposals
function castVotesWithReason(
uint256[] calldata proposalIds,
uint8[] calldata supportList,
string[] calldata reasonList_
) external returns (uint256 weight);
// Gasless signature variants also exist (e.g., castVotesBySig)
```
This reduces gas costs and improves the user experience for active participants.
import ZoomableImage from '@/components/ZoomableImage';
## M0 on Solana: Technical Deep Dive
This document provides a detailed technical overview of the M0 Protocol's implementation on the Solana blockchain. It is
intended for developers who want to integrate with or build on top of `$M` and its extensions on Solana.
It assumes you are familiar with Solana's programming model (programs, accounts, CPIs) and have a conceptual
understanding of M0's hub-and-spoke architecture.
### Architecture & Core Components
M0's Solana deployment functions as a "spoke" in the cross-chain model, with Ethereum as the "hub". Communication and
token transfers are managed by the **M Portal**, which is a customized implementation of Wormhole's **Native Token
Transfer (NTT)** framework.
The system is composed of three core onchain programs and several offchain services that work in concert.
The core onchain programs for M0 on Solana.
#### Onchain Programs
The onchain logic is modular, distributed across three main Solana programs written in Rust using the Anchor framework.
| Program | Mainnet Program ID | Devnet Program ID | Description |
| ----------------- | ---------------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Portal** | `mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY` | `mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY` | The gateway for bridging `$M` between EVM chains and Solana. A fork of Wormhole's NTT program with M0-specific payload support and executor integration. |
| **Earn** | `MzeRokYa9o1ZikH6XHRiSS5nD8mNjZyHpLCBRTBSY4c` | `MzeRokYa9o1ZikH6XHRiSS5nD8mNjZyHpLCBRTBSY4c` | Manages yield distribution and earner permissions for the base `$M` token with frozen-by-default security model. |
| **M Extensions** | `3C865D264L4NkAm78zfnDzQJJvXuU3fMjRUvRxyPi5da` | `3C865D264L4NkAm78zfnDzQJJvXuU3fMjRUvRxyPi5da` | Unified extension framework (`m_ext`) compiled with feature flags for NoYield, ScaledUi, and Crank models. |
| **Swap Facility** | `MSwapi3WhNKMUGm9YrxGhypgUEt7wYQH3ZgG32XoWzH` | `MSwapi3WhNKMUGm9YrxGhypgUEt7wYQH3ZgG32XoWzH` | Permissioned router for atomic swaps between whitelisted M0 extensions (`ext_swap` program). |
#### Key Onchain Assets
| Asset | Mainnet Mint Address | Devnet Mint Address | Description |
| ------------- | --------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **$M Token** | `mzerokyEX9TNDoK4o2YZQBDmMzjokAeN6M2g2S3pLJo` | `mzeroZRGCah3j5xEWp2Nih3GDejSBbH1rbHoxDg8By6` | The base yield-bearing stablecoin, implemented as a Token-2022 asset with frozen-by-default security. |
| **$wM Token** | `mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp` | `mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp` | The wrapped, 1:1 `$M`-backed extension using the Crank model, also a Token-2022 asset. |
### V2 Upgrades: What Changed
The V2 upgrade introduced seven major enhancements to the Solana implementation:
#### 1. Direct Bridging to Extensions
**What Changed:** The Portal now supports a `destination_token` field in the `AdditionalPayload`, allowing users to
bridge directly to extension tokens (like `$wM`) instead of only receiving base `$M`.
**Why It Matters:** Eliminates the need for a separate wrap transaction after bridging, improving UX and reducing costs.
**Technical Implementation:**
```rust
pub struct AdditionalPayload {
pub index: u64,
pub destination_token: [u8; 32], // NEW: Target mint address (M or extension)
pub earner_root: Option<[u8; 32]>,
}
```
#### 2. Frozen-by-Default $M
**What Changed:** All new `$M` token accounts are initialized with `AccountState::Frozen` using Token-2022's
`DefaultAccountState` extension.
**Why It Matters:** Enhances security by preventing unauthorized accounts from holding `$M`. Only approved earners
(verified via Merkle proof) can have their accounts thawed.
**Technical Implementation:**
```rust
// $M mint is created with DefaultAccountState::Frozen
let default_state = DefaultAccountState {
state: AccountState::Frozen,
};
```
#### 3. Onchain Yield Distribution
**What Changed:** Yield distribution is now fully onchain via the scaled-ui mechanism, replacing V1's centralized
distribution.
**Why It Matters:** Increases transparency and decentralization. All yield calculations and distributions are verifiable
onchain.
**Technical Implementation:**
* Yield distributed via scaled-ui mechanism
* Index updates propagated onchain
* Extensions automatically sync on wrap/unwrap operations
#### 4. Permissionless Index Updates
**What Changed:** The `sync()` instruction for extension index updates is now permissionless—anyone can call it.
Additionally, anyone can bridge an index update, and extensions automatically sync on wrap/unwrap operations.
**Why It Matters:** Enables keeper bots and users to ensure yield indices are always up-to-date without relying on
centralized services.
**Who Can Call:** Anyone (no signer required for sync, though accounts must be valid). Index updates can also be bridged
permissionlessly.
#### 5. Executor Support for Generic Bridge Messages
**What Changed:** Integration with Wormhole's executor pattern (`executor-account-resolver-svm`) enables arbitrary
cross-chain instructions to be executed atomically with bridge transfers.
**Why It Matters:** Unlocks advanced cross-chain composability, such as "bridge and stake" or "bridge and swap" in a
single transaction.
**Technical Implementation:**
* Portal validates executor messages
* Executor Account Resolver resolves required accounts
* Arbitrary CPI instructions executed after mint
#### 6. Unified Extension Framework
**What Changed:** All extensions (NoYield, ScaledUi, Crank) are now compiled from a single `m_ext` program using Rust
feature flags.
**Why It Matters:** Reduces code duplication, simplifies auditing, and ensures consistent behavior across all extension
types.
**Feature Flags:**
```rust
[features]
no-yield = []
scaled-ui = []
crank = []
```
#### 7. Multi-Network Support (Fogo Compatibility)
**What Changed:** V2 architecture is designed to support both Solana and Fogo (Wormhole's native rollup) with minimal
changes.
**Why It Matters:** Future-proofs the protocol for multi-network deployment and leverages Wormhole's native cross-chain
capabilities.
**Implementation:** Feature flags enable Fogo-specific configurations without forking the codebase.
### Program Specifications
#### 1. Portal Program (`programs/portal`)
The Portal is the entry and exit point for `$M` into the Solana ecosystem.
##### Core Logic & Custom Payload
* **Core Logic:** Based on Wormhole NTT, it validates Wormhole Verifiable Action Approvals (VAAs) and processes
inbound/outbound transfers. The key inbound instruction is `release_inbound_mint_multisig`, which mints `$M` tokens
and makes a CPI to the `Earn` program's `propagate_index` function.
* **Custom Payload (`payloads/token_transfer.rs`):** The standard `NativeTokenTransfer` struct is extended with an
`AdditionalPayload`. This is the most critical customization, allowing M0-specific data to be securely transmitted
with every bridge transfer.
```rust
// programs/portal/src/payloads/token_transfer.rs
pub struct AdditionalPayload {
pub index: u64, // Latest $M Earning Index from Ethereum
pub destination_token: [u8; 32], // Target token mint (M or extension)
pub earner_root: Option<[u8; 32]>, // Merkle root of approved earners
}
```
* **`index`:** The latest `$M` Earning Index from Ethereum.
* **`destination_token`:** The target token mint address on Solana (e.g., `$M` or `$wM`). **NEW in V2.**
* **`earner_root`:** An optional Merkle root of the official `$M` earner list, as determined by M0 Governance on
Ethereum.
##### Executor Integration
V2 Portal integrates with Wormhole's executor pattern to enable generic cross-chain instructions:
```rust
// Executor message structure
pub struct ExecutorMessage {
pub instruction_data: Vec,
pub program_id: Pubkey,
pub account_metas: Vec,
}
```
The executor account resolver (`executor-account-resolver-svm`) resolves required accounts and executes instructions
atomically with bridge transfers.
#### 2. Earn Program (`programs/earn`)
This program manages yield and permissions for the base `$M` token on Solana.
##### V2 Security Model: Frozen by Default
All `$M` token accounts are initialized with `AccountState::Frozen`. Accounts are only thawed after:
1. User submits Merkle proof via `add_registrar_earner`
2. Proof is verified against `earner_merkle_root`
3. Account state is updated to `AccountState::Initialized`
##### State (`state/`)
* **`Global` Account (`state/global.rs`):** A PDA seeded with `b"global"` that stores the program's configuration.
* `admin`: The administrative authority.
* `earn_authority`: A permissioned key that can call `claim_for` instructions.
* `index`: The most recently propagated `$M` Earning Index.
* `claim_cooldown`: The minimum time between yield claim cycles.
* `max_yield` & `distributed`: Used to track and cap the amount of yield distributed in a cycle.
* `claim_complete`: A flag indicating if the current claim cycle has finished.
* `earner_merkle_root`: The Merkle root of the governance-approved earner list.
* **`Earner` Account (`state/earner.rs`):** A PDA seeded with `b"earner"` and the user's token account address. It
tracks an individual's earning status.
* `last_claim_index`: The index at which the user last received yield.
* `user`: The wallet address of the earner.
* `user_token_account`: The specific token account that is earning yield.
##### Key Instructions (`instructions/`)
* **`propagate_index`:** Called by the Portal via CPI. It updates the `Global` account's `index` and
`earner_merkle_root`, and can initiate a new claim cycle if conditions are met (cooldown passed, new yield available).
* **`add_registrar_earner`:** A **permissionless** instruction allowing a user to prove their eligibility to earn yield.
The user provides a Merkle proof that their address is included in the `earner_merkle_root`. **On success, thaws the
frozen token account.**
* **`claim_for`:** A permissioned instruction called by the `earn_authority`. It calculates the yield owed to a specific
earner since their last claim and mints new `$M` tokens to their account. **NEW in V2: fully onchain distribution.**
* **`complete_claims`:** Called by the `earn_authority` to mark the end of a yield distribution cycle.
* **`sync`:** **NEW in V2.** Permissionless instruction to update the earning index. Anyone can call this to keep
indices current.
#### 3. $M Extension Framework (`programs/m_ext`)
**NEW in V2:** Unified extension framework replacing separate programs for each model.
##### Architecture
All extension models are compiled from a single `m_ext` program using Rust feature flags:
```rust
// Cargo.toml
[features]
no-yield = []
scaled-ui = []
crank = []
```
**Deployed Instances:**
* **NoYield Extensions:** Compiled with `--features no-yield`
* **ScaledUi Extensions:** Compiled with `--features scaled-ui`
* **Crank Extensions:** Compiled with `--features crank` (used by `$wM`)
##### State Structures
**ExtGlobal Account (shared across all models):**
```rust
pub struct ExtGlobal {
pub admin: Pubkey,
pub earn_authority: Option, // Only for Crank model
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,
}
pub struct YieldConfig {
pub variant: YieldVariant, // NoYield | ScaledUi | Crank
pub last_m_index: u64,
pub last_ext_index: u64,
}
pub enum YieldVariant {
NoYield,
ScaledUi,
Crank,
}
```
**Crank-Specific State:**
```rust
// Only present when compiled with 'crank' feature
pub struct EarnManager {
pub manager: Pubkey,
pub fee_bps: u64,
pub fee_token_account: Pubkey,
pub bump: u8,
}
pub struct Earner {
pub user: Pubkey,
pub user_token_account: Pubkey,
pub recipient_token_account: Pubkey,
pub earn_manager: Pubkey,
pub last_balance: u64,
pub last_index: u64,
pub bump: u8,
}
```
##### Core Instructions (Shared)
* **`initialize`:** Sets up the extension's global state
* **`wrap(amount: u64)`:** Wraps `$M` into extension token
* **`unwrap(amount: u64)`:** Unwraps extension token back to `$M`
* **`add_wrap_authority` / `remove_wrap_authority`:** Manage wrap permissions
* **`transfer_admin`:** Transfer admin control
##### Model-Specific Instructions
**NoYield:**
* `claim_fees()`: Admin claims all accrued yield
**ScaledUi:**
* `sync()`: Permissionless index update (updates scaled-ui rate)
* `set_fee(fee_bps: u64)`: Set optional fee (typically 0 for full pass-through)
**Crank:**
* `add_earn_manager` / `remove_earn_manager`: Admin manages earn managers
* `add_earner` / `remove_earner`: Earn manager manages earners
* `claim_for(user, snapshot_balance)`: Earn authority distributes yield
* `sync()`: Update extension index from base $M program
* `set_recipient`: Earner sets custom yield recipient
#### 4. Swap Facility (`programs/ext_swap`)
**NEW in V2:** Centralized router for atomic swaps between whitelisted extensions.
##### Global State
```rust
pub struct SwapGlobal {
pub bump: u8,
pub admin: Pubkey,
pub whitelisted_unwrappers: Vec,
pub whitelisted_extensions: Vec,
}
pub struct WhitelistedExtension {
pub program_id: Pubkey,
pub ext_global: Pubkey,
}
```
##### Core Instructions
* **`swap(amount, remaining_accounts_split_idx)`**: Atomic cross-extension swap
* **`wrap(amount)`**: Convert `$M` to extension
* **`unwrap(amount)`**: Convert extension to `$M` (requires `whitelisted_unwrapper`)
* **`whitelist_extension` / `remove_whitelisted_extension`**: Admin manages extensions
* **`whitelist_unwrapper` / `remove_whitelisted_unwrapper`**: Admin manages unwrap permissions
**Security Model:**
* Only whitelisted extensions can participate in swaps
* Only whitelisted unwrappers can call `unwrap` directly
* All swaps maintain 1:1 peg
### Developer Integration
#### Interacting via SDK
The recommended way to interact with the M0 Solana programs is through the official TypeScript SDK.
* **Installation:** `pnpm i @m0-foundation/solana-m-sdk`
* **Core Classes:** The SDK provides classes that abstract away the onchain complexity:
* `EarnAuthority`: For admin-level actions like initiating claims and syncing indexes.
* `EarnManager`: For managing a set of earners within an extension (Crank model).
* `Earner`: For user-level actions and querying an individual's yield status.
**Example: Building a Claim Transaction (V2 Crank Model)**
```tsx
import { EarnAuthority, Earner } from '@m0-foundation/solana-m-sdk';
import { Connection, PublicKey } from '@solana/web3.js';
import { createPublicClient, http } from 'viem';
// Setup clients
const connection = new Connection(RPC_URL);
const evmClient = createPublicClient({ transport: http(EVM_RPC_URL) });
// Load authority and earner
const auth = await EarnAuthority.load(connection, evmClient, M_EXT_PROGRAM_ID);
const earners = await auth.getAllEarners();
const earnerToClaim = earners[0];
// Build the claim instruction
const claimIx = await auth.buildClaimInstruction(earnerToClaim);
if (claimIx) {
// build, sign, and send transaction with the instruction...
}
```
**Example: Permissionless Sync (V2 ScaledUi Model)**
```tsx
import { Program } from '@coral-xyz/anchor';
// Anyone can call sync to update yield indices
const tx = await program.methods
.sync()
.accounts({
extGlobal: extGlobalPDA,
extMint: extMintAddress,
mEarnGlobal: mEarnGlobalAddress,
mVault: mVaultPDA,
token2022Program: TOKEN_2022_PROGRAM_ID,
})
.rpc();
console.log('Synced yield:', tx);
```
#### Offchain Data via API
For historical data, analytics, and building dashboards, use the M0 Solana API, which is powered by a Substreams
indexer.
* **Base URL (Mainnet):** `https://api-production-0046.up.railway.app`
* **SDK:** `pnpm i @m0-foundation/solana-m-api-sdk`
* **Key Endpoints:**
* `/events/bridges`: Get latest bridge events.
* `/events/index-updates`: Get historical `$M` Index updates.
* `/token-account/{pubkey}/{mint}/claims`: Get yield claim history for a specific token account.
#### Onchain Addresses
A full list of program IDs, mints, and other key accounts for both Mainnet and Devnet can be found in the
[**Addresses resource page**](/get-started/resources/addresses/#solana).
### Fogo Network Support
**NEW in V2:** The architecture is designed to support Fogo, Wormhole's native rollup, alongside Solana.
**What is Fogo?**
* Wormhole-native rollup optimized for cross-chain applications
* SVM-compatible (runs Solana programs with minimal changes)
* Optimized for low-latency cross-chain messaging
**M0 on Fogo:**
* Same program logic as Solana deployment
* Feature flags enable Fogo-specific optimizations
* Seamless interoperability with Solana via Wormhole NTT
**Deployment Status:** Fogo support is live. Check the [Addresses page](/get-started/resources/addresses/) for
Fogo-specific program IDs.
### Source Code & Audits
* **Portal & Earn Programs:** [m0-foundation/solana-m](https://github.com/m0-foundation/solana-m)
* **Extension Framework & Swap Facility:**
[m0-foundation/solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions)
* **Audits:** Security audits for all programs can be found in the
[**Audits resource page**](/get-started/resources/audits/).
### Next Steps
* **Build with $M:** Integrate the base `$M` token into your Solana dApp
* **Create an Extension:** Deploy your own `$M`-backed stablecoin using NoYield, ScaledUi, or Crank models
import ZoomableImage from '@/components/ZoomableImage';
## Roles
### Introduction
The M0 Protocol operates through a carefully designed system of actors, each with distinct roles and responsibilities
crucial for the protocol's operation, security, and decentralized governance. Understanding these roles is key to
understanding how `$M` is created, secured, utilized, and how the protocol evolves.
The protocol's core functionality is implemented in several key smart contracts that these roles interact with:
* **`MToken`**: The ERC20-compliant token contract for `$M`, which includes the unique earning capabilities.
* **`MinterGateway`**: The interface for minting and burning, managing Minter collateral, and tracking debt obligations.
* **`TTGRegistrar`**: The central configuration store managed by Two-Token Governance (TTG), holding approved addresses
for roles and key protocol parameters.
Below is a high-level overview of the primary roles within the M0 ecosystem, categorized by their main area of
interaction.
### Overview of Key Protocol Roles
The M0 ecosystem comprises several key actors, each playing a vital part:
**1. $M Supply & Security:**
* **Minters**:
* **Description**: Minters are permissioned institutions responsible for creating (minting) new `$M`. They are the
primary on/off-ramp to and from fiat.
* **Function**: They provide eligible offchain collateral (such as U.S. backed reserves held in Special Purpose
Vehicles) and register its value onchain via the MinterGateway. Based on this collateral and governance-set ratios,
they can mint. They also pay interest on their minted supply, which forms the basis for the yield distributed to
Earners and the protocol.
* **Validators**:
* **Description**: Validators are independent, trusted entities acting as a crucial security and verification layer
for the protocol.
* **Function**: Their primary role is to verify the offchain collateral reported by Minters, ensuring that all supply
remains fully and transparently backed. They provide cryptographic attestations (signatures) for Minters' collateral
updates to the MinterGateway. Validators also possess emergency powers, such as freezing a Minter or canceling a
suspicious mint proposal, to safeguard protocol integrity.
**2. $M Usage & Yield:**
To earn yield, a user has to get the earner status. Below is an overview of the Earner status:
* **Description**: Earners are addresses (users, smart contracts, or M0 Extensions) that have been approved by M0
Governance (or delegated admins for certain extensions like wM) to accrue yield on their `$M` balances.
* **Function**: Once approved and activated via MToken.startEarning(), their balance (or the balance of an M0-powered
extension they hold) automatically increases over time due to continuous compounding, based on the prevailing earner
rate. This mechanism allows `$M` to function as a yield-bearing component for authorized participants.
**3. Protocol Governance:**
* **Power token (POWER) Holders**:
* **Description**: POWER Holders are the primary participants in the operational governance of the M0 Protocol.
* **Function**: POWER tokens are used to vote on proposals in the `StandardGovernor` (e.g., approving `Minters`,
`Validators`, `Earners`; setting operational parameters like interest rates or collateral ratios) and
the `EmergencyGovernor` (for urgent protocol adjustments). Active and complete participation
in `StandardGovernor` voting is incentivized through POWER token inflation and ZERO token rewards.
* **Zero token (ZERO) Holders**:
* **Description**: ZERO Holders possess meta-governance authority, effectively acting as the highest level of
governance oversight for the protocol.
* **Function**: ZERO holders are entitled to claim a pro-rata share of protocol revenues (e.g., excess Minter yield,
Power token auction proceeds) accumulated in the DistributionVault. Additionally, ZERO tokens are used to vote in
the ZeroGovernor on fundamental changes, such as upgrading the core governance contracts themselves, altering
critical system parameters (like governance thresholds), or changing the accepted CashTokens for proposal fees.
Visual to point to which contracts a role / major protocol / governance part it is interacting with:
### Minters
#### Role Definition
Minters are entities authorized to generate `$M` by securing Eligible Collateral in offchain Special Purpose Vehicles
(SPVs) and maintaining an onchain Collateral Value that represents these offchain assets.
#### Key Functions
Minters interact with the protocol primarily through the `MinterGateway` contract:
#### 1. Activation and Status Management
* `activateMinter(address minter_)`: A newly approved (by governance) minter must be activated before they can mint.
This explicit step stores the active status locally within `MinterGateway` for gas efficiency, avoiding repeated
external checks.
* Once deactivated, a minter cannot be reactivated. This permanence also optimizes gas by eliminating the need for
further status checks against the external `TTGRegistrar`.
#### 2. Collateral Management
* `updateCollateral(collateral, retrievalIds, metadataHash, validators, timestamps, signatures)`: Updates collateral
value with validator signatures
* `proposeRetrieval(collateral)`: Proposes to retrieve excess collateral
* Must maintain sufficient collateral to support minted $M tokens according to the governance-set mint ratio
#### 3. Minting Process
* `proposeMint(amount, destination)`: Proposes to mint a specific amount of tokens
* `mintM(mintId)`: Executes a previously proposed mint after the mint delay period and before mint time-to-live period
#### Lifecycle and Constraints
Minters go through several states:
1. **Inactive**: Approved by governance but not activated
2. **Active**: Called `activateMinter()` and can mint tokens
3. **Frozen**: Temporarily restricted from minting (but can still burn)
4. **Deactivated**: Permanently removed from the system
Minters face several key constraints:
* Must update collateral within the required interval (typically daily). As of today, the update collateral interval is
set to 30 hours as per
[this proposal](https://governance.m0.org/proposal/17943707564765307947230727208177770356513863441428702266086965014917482113266).
* Must maintain collateralization above the mint ratio
* Pay interest on minted tokens at the minter rate
* Face penalties for missed updates or undercollateralization
#### Penalties
Minters can receive penalties in two cases:
1. **Missed Updates Penalty**: Applied when minters fail to update collateral within required intervals
* Calculated as `penaltyRate × principalOfActiveOwedM × missedIntervals`
* Charged only once per missed interval
2. **Undercollateralization Penalty**: Applied when active owed $M exceeds allowed maximum
* Calculated as `penaltyRate × principalOfExcessOwedM × timeSpan / updateCollateralInterval`
* Proportional to the duration of undercollateralization
### Validators
#### Role Definition
Validators serve as a critical security layer, verifying the accuracy of Minters' collateral updates and having
emergency powers to protect the protocol. They are trusted entities responsible for bridging the gap between offchain
assets and onchain accounting.
#### Key Functions
Validators interact with the protocol primarily through signature generation and special authority functions:
#### 1. Collateral Verification
* Sign offchain messages (`updateCollateral` digest) verifying collateral updates, which includes the list of retrieval
proposals being resolved.
* Signatures include timestamps to prevent replay attacks
* Multiple validators must sign collateral updates to meet the threshold
#### 2. Security Controls
* `cancelMint(address minter, uint256 mintId)`: Cancel suspicious mint proposals
* `freezeMinter(address minter)`: Temporarily freeze a minter's ability to mint new tokens
#### 3. Signature Requirements
* Signatures must be provided in ascending order by validator address
* Each signature must have a timestamp newer than the last signature from that validator
* The protocol enforces a threshold of validator signatures for valid collateral updates
#### Security Mechanisms
Validators implement several security protections:
* **Timestamp Verification**: Ensures signatures aren't reused or used out of order
* **Signature Uniqueness**: Each validator can only provide one valid signature per collateral update
* **Emergency Powers**: Ability to cancel mints and freeze Minters to mitigate potential harm
### Earners
#### Role Definition
Earners are approved addresses that can enable earning on their $M token balance, allowing them to receive yield based
on the protocol's earning rate. This is a key feature that separates $M from traditional stablecoins.
#### Key Functions
Earners interact with the protocol primarily through the `MToken` contract:
#### 1. Earning Status Management
* `startEarning()`: Converts a regular balance to an earning balance (requires TTG approval)
* `stopEarning()`: Converts an earning balance back to a regular balance (keeps all accrued interest)
* `stopEarning(address)`: Safety function allowing anyone to stop earning for an account that's been removed from the
approved list
#### 2. Balance Mechanism
When an account is in earning status:
* Their balance automatically increases over time through continuous compounding
* Balances are stored internally as "principal amounts" that grow with the earning index
* No transactions are needed to accrue interest
#### Rate Mechanics
Earners receive yield based on the Earner Rate, which is determined by a sophisticated rate model:
* The rate model ensures yield to earners never exceeds interest paid by minters
* Uses a mathematical formula with both instantaneous and time-integrated approaches
* Applied with a safety factor (98% of the theoretical safe rate)
* Continuously updated when key functions are called
### Regular $M Holders
#### Role Definition
Regular `$M` holders use it as a stablecoin without the earning feature. Their balances remain static and only change
through explicit transactions.
#### Key Functions
Regular `$M` holders have standard ERC20 capabilities with additional features:
#### Token Operations
* Standard transfers (`transfer`, `transferFrom`), approvals (`approve`), and balance inquiries (`balanceOf`).
* Support for gasless approvals through EIP-2612 permits (`permit`).
* Support for transfers with authorization through EIP-3009 (`transferWithAuthorization`, `receiveWithAuthorization`).
#### Transfer Mechanics
Transfers between different types of accounts follow specific rules:
* Between two non-earning accounts: Standard token transfer.
* Between non-earning and earning accounts: Balance conversion occurs, using protocol-favoring rounding (principal
rounded down on receive, principal rounded up on send).
* All transfers are subject to consistency checks and appropriate balance updates in the `MToken` contract.
**Note on Minter Debt Repayment**: Regular `$M` holders can choose to interact with the `MinterGateway`'s `burnM`
function. This allows them to burn their *own* tokens to help repay the outstanding debt of a specific Minter (either
active or deactivated), acting as an indirect way to support protocol stability or participate in the informal
liquidation of a deactivated minter's position. This is distinct from standard token operations.
### Power token Holders
#### Role Definition
Power token holders act as the primary governance participants, responsible for day-to-day protocol governance through
the `StandardGovernor` and emergency actions through the EmergencyGovernor.
#### Key Functions
Power token holders can influence the protocol through several mechanisms:
#### 1. Standard Governance
* Vote on adding/removing addresses from minter/validator/earner lists
* Vote on key protocol parameters like mint ratio, collateral update interval, etc.
* Propose and vote on protocol parameter changes
* `StandardGovernor` proposals require a simple majority to pass (yes votes > no votes)
#### 2. Emergency Governance
* Vote on emergency actions through the EmergencyGovernor
* Can take immediate action to protect the protocol in emergency situations
* EmergencyGovernor uses a threshold ratio mechanism requiring a minimum percentage of yes votes
#### 3. Token Mechanics
* Power token is inflationary (10% per active voting epoch)
* Token holders can directly vote or delegate their voting power
* Snapshots of balances, voting powers, and delegations occur at epoch boundaries
* Inflation rewards are only given to those who vote on ALL proposals in a voting epoch
* Cannot transfer/delegate/mint during odd epochs (voting epochs)
* Voting power is calculated from the previous epoch's balance
#### 4. Dutch Auction
* Can buy Power tokens during Dutch auctions during non-voting epochs
* Price decreases exponentially over time, with the slope halving every period
* Purchase price is relative to the previous epoch's total supply
* Payments go to the Distribution Vault
### Zero token Holders
#### Role Definition
Zero token holders possess meta-governance rights, essentially serving as the highest level of governance in the
protocol. They have the unique ability to completely redeploy the governance system and receive rewards from excess
yield and other protocol fees.
#### Key Functions
Zero token holders have special privileges in the protocol:
#### 1. Meta-Governance
* Can vote to completely redeploy the standard and emergency governance systems through the ZeroGovernor
* Can reset governance to either the current Power token or Zero token holders (complete governance reset)
* Can update the threshold ratio for proposals in ZeroGovernor or EmergencyGovernor
* Can set new cashTokens for the entire system
* ZeroGovernor proposals require a minimum threshold of votes to pass
#### 2. Reward Mechanism
* Receive Zero token rewards for participation in `StandardGovernor` voting
* Claim excess yield and fees from the Distribution Vault
* Buy Power token with a discount during Dutch auctions
#### 3. Token Mechanics
* ERC20 token with 6 decimals
* Supports EIP-3009, EIP-712 with EIP-1271 support, and EIP-5805
* Can only be minted by `StandardGovernor` as reward for voting participation
* Used for voting in ZeroGovernor
* Epochs are 15 days long with snapshots taken at the end of each epoch
### Role Interactions and System Balance
The M0 Protocol maintains balance through carefully designed interactions between roles:
#### Minter-Validator Relationship
* Minters propose collateral updates and mint requests
* Validators verify collateral and can intervene when necessary
* This creates a checks-and-balances system for minting activity
#### Minter-Earner Balance
* Minters pay interest on borrowed tokens (minter rate)
* Earners receive interest on their holdings (earner rate)
* Rate models ensure earner yield never exceeds minter interest obligations
#### Governance Hierarchy
* Power token holders control standard parameter changes through `StandardGovernor`
* Zero token holders have meta-governance capabilities through ZeroGovernor
* EmergencyGovernor provides quick response capability for urgent issues
* This creates a tiered governance structure for different types of decisions
#### Distribution Vault Mechanism
* The DistributionVault receives excess yield generated by Minters not distributed to Earners
* It also receives cash tokens from Power token auctions and unrefunded proposal fees
* Zero token holders can claim their pro-rata share of these accumulated assets
* This provides an incentive mechanism for Zero token holders to participate in governance
#### Cash Token System
* Cash tokens are used to pay proposal fees in `StandardGovernor`
* They're also used to purchase Power tokens in Dutch auctions
* Expected to be WETH and $M according to the Whitepaper
* Must be in an allowed set defined at ZeroGovernor deployment
* Can be changed via ZeroGovernor proposals
#### Economic Incentives
* Minters are incentivized to maintain proper collateralization
* Validators are trusted to verify offchain collateral accurately
* Earners benefit from yield generated by the system
* Power token holders are rewarded with inflation for active participation in all proposals
* Zero token holders can claim excess yield and fees while maintaining meta-governance rights
### Trust Model
The protocol operates with the following trust assumptions:
* **Validators**: Fully trusted to verify offchain collateral accurately and monitor onchain activities. They must be
reachable and able to issue valid signatures for minters in a timely manner.
* **Minters**: Trusted to maintain their offchain collateral and meet protocol obligations. Minters are expected to
update their collateral at required intervals and pay any imposed penalties.
* **`$M` holders**: Not trusted, but can freely use the token as a stablecoin without special permissions.
* **Power token holders**: Trusted to act in the best interest of the protocol for day-to-day governance. They are
expected to vote on proposals and participate actively in governance.
* **Zero token holders**: Trusted with meta-governance authority to reset the entire governance system if necessary.
They have the highest level of governance power and are assumed to act as responsible stewards of the protocol.
* **CashTokens**: Expected to be WETH and $M as stated in the Whitepaper, but nothing prevents deployment with other
cashTokens. These must be ERC20-compliant without special behavior (such as transfer hooks, rebasing mechanisms, or
transfer fees).
The protocol incorporates several safeguards against potential trust violations:
1. Multi-level governance (`StandardGovernor`, EmergencyGovernor, ZeroGovernor)
2. Collateral verification through multiple validators
3. Penalty systems for minters who don't follow protocol rules
4. Economic incentives aligning with protocol health for all participants
This trust model creates a balanced system where each role has appropriate permissions and incentives to maintain
protocol stability and security.
import ZoomableImage from '@/components/ZoomableImage';
## Rates Models & Yield
### 1. The Interest Mechanism
$M token operates within a balanced interest system in the M0 Protocol:
1. **Minter Interest Accrual**: Minters accrue obligations at the minter rate on their outstanding $M balances.
2. **Earner Interest Accrual**: Simultaneously, approved earning accounts accrue additional tokens at the earner rate.
3. **Rate Synchronization**: The EarnerRateModel ensures the earner rate is calibrated so total earnings never exceed
total minter obligations (see Rate Model section below for further details).
4. **Continuous Mechanism**: Both sides update through independent but synchronized continuous indexing systems.
This creates a mathematically balanced system where earner yields are safely constrained by the protocol's income from
minters.
$M token implements sophisticated interest rate models that ensure financial soundness while distributing yield between
minters, earners, and the protocol.
### 2. Rate Model Architecture
The protocol uses two primary rate models:
1. **MinterRateModel**: Determines the interest rate minters pay on borrowed $M token
* Simple model that reads rates directly from the TTG Registrar
* Capped at 400% APR (40,000 basis points) for system safety
* Governance-controlled through the `base_minter_rate` parameter
2. **EarnerRateModel**: Calculates the interest rate paid to $M token holders who opt in to earning
* Uses a mathematical formula that ensures financial soundness
* Rates are capped at 98% of the calculated safe rate
* Governed through the `max_earner_rate` parameter
#### Minter Rate Calculation
The `MinterRateModel` has a straightforward implementation:
```math
minterRate = min(\text{ TTGRegistrar({\text{BASE-MINTER-RATE}})}, \text{MAX-MINTER-RATE})
```
```solidity
function rate() external view returns (uint256) {
return min(TTGRegistrar.get("base_minter_rate"), MAX_MINTER_RATE);
}
```
* The rate is governance-controlled through the TTG Registrar
* Has a hard cap of 400% APR to prevent system abuse
* Changes to this rate affect all minters uniformly
#### Earner Rate Calculation
The EarnerRateModel employs a more complex approach to ensure system solvency:
```math
earnerRate = min(maxRate,getExtraSafeEarnerRate)
```
```math
getExtraSafeEarnerRate = safeEarnerRate\times {RateMultiplier}
```
```solidity
function rate() external view returns (uint256) {
return min(
maxRate(),
getExtraSafeEarnerRate(
totalActiveOwedM,
totalEarningSupply,
minterRate
)
);
}
```
Where:
* `getExtraSafeEarnerRate` applies the rate multiplier to the safe earner rate
* `RATE_MULTIPLIER` is 9,800 (98% in basis points)
* `ONE` is 10,000 (100% in basis points)
* `maxRate()` returns the governance-set maximum earner rate
##### Safe Rate Derivation
The safe earner rate is calculated using two different approaches based on the relationship between total active owed $M
and total earning supply:
1. **When totalActiveOwedM ≤ totalEarningSupply**:
* Uses an instantaneous cashflow approach
* `safeRate = totalActiveOwedM × minterRate / totalEarningSupply`
* Ensures interest paid to earners never exceeds interest collected from minters
2. **When totalActiveOwedM > totalEarningSupply**:
* Uses a time-integrated approach over a 30-day confidence interval
* Accounts for compound interest effects
* Applies complex mathematical formula to ensure long-term solvency
* Allows earner rate to temporarily exceed minter rate while maintaining system safety
##### Extra Safety Factor
The EarnerRateModel applies an additional safety factor to the calculated safe rate:
```solidity
function getExtraSafeEarnerRate(
uint240 totalActiveOwedM_,
uint240 totalEarningSupply_,
uint32 minterRate_
) public pure returns (uint32) {
uint256 safeEarnerRate_ = getSafeEarnerRate(totalActiveOwedM_, totalEarningSupply_, minterRate_);
uint256 extraSafeEarnerRate_ = (safeEarnerRate_ * RATE_MULTIPLIER) / ONE;
return (extraSafeEarnerRate_ > type(uint32).max) ? type(uint32).max : uint32(extraSafeEarnerRate_);
}
```
This ensures that earners receive 98% of the theoretically safe rate, creating an additional buffer for protocol safety.
### Confidence Interval Approach
The EarnerRateModel uses a 30-day confidence interval to determine safe rates:
* Calculates the amount of interest minters will generate over 30 days
* Derives maximum earner rate that ensures total interest paid doesn't exceed this amount
* Accounts for the compounding effects of continuous interest
* Assumes the `updateIndex()` function will be called at least once every 30 days
### Balance Between Minters and Earners
The system maintains a mathematical relationship between interest flows:
```
totalInterestFromMinters = totalActiveOwedM × (e^(minterRate×Δt) - 1)
totalInterestToEarners = totalEarningSupply × (e^(earnerRate×Δt) - 1)
```
The rate models ensure: `totalInterestToEarners ≤ 98% of totalInterestFromMinters`
### Edge Cases Handling
The EarnerRateModel handles several edge cases with special logic:
* When `totalActiveOwedM = 0` or `minterRate = 0`: The earner rate is set to 0
* When `totalEarningSupply = 0`: The earner rate is set to the maximum possible value (`type(uint32).max`)
* When calculations might overflow: The implementation includes safeguards against numerical issues
### Yield Distribution
The system distributes yield in the following manner:
1. **Minters pay interest** based on their outstanding debt at the minter rate
2. **Earners receive up to 98%** of the available interest (subject to constraints)
3. **Excess yield** (at least 2% plus any additional buffer) goes to the TTG Vault
4. The TTG Vault funds protocol operations and can distribute to governance token holders
### Rate Update Mechanism
Interest rates don't change automatically when governance parameters are updated:
1. Rate changes in the TTG Registrar take effect only when `updateIndex()` is called
2. Both $M token and MinterGateway must update their respective indices
3. Updates occur automatically during key operations such as minting, burning, and transfers
4. Rate models recalculate rates during each update based on current system state
### Mathematical Precision
The implementation includes several technical considerations:
* Rates are stored in basis points (1/100 of a percent) for precision
* Calculations use fixed-point math with 12 decimal places (1e12 scaling)
* Exponential calculations use Padé approximants for gas-efficient computation
* Conservative rounding strategies favor protocol safety
### Visuals
## MinterGateway
*Where money is born.*
The MinterGateway contract serves as the central hub for minting and burning MTokens, managing minter collateral, and
tracking debt obligations in the M0 Protocol.
### Minter Types and Lifecycle
The system recognizes several states for minters:
1. **Approved**: Minters whitelisted by the TTG governance system
2. **Active**: Approved minters who have activated their minting capabilities
3. **Frozen**: Active minters temporarily prevented from minting (but can still burn)
4. **Deactivated**: Former minters who have exited the system (cannot be reactivated)
### Collateral Management
Unlike traditional onchain collateral systems, M0 uses a validator-verified offchain collateral model:
* **Real-World Backing**: Collateral exists in a Special Purpose Vehicle (SPV) as real-world assets
* **Validator Verification**: Trusted validators periodically verify and sign off on collateral values
* **Update Requirements**: Minters must update their collateral regularly (typically daily)
* **Expiration Mechanism**: Collateral is considered zero if not updated within the specified interval
* **Update Signatures**: Multiple validator signatures are required, with the minimum threshold set by governance
#### Minting Process
Creating new MTokens follows a structured three-step process designed to enhance security and enable validator
oversight:
1. **Update Collateral**: Minters provide validator signatures confirming offchain collateral
```solidity
function updateCollateral(
uint256 collateral,
uint256[] calldata retrievalIds,
bytes32 metadataHash,
address[] calldata validators,
uint256[] calldata timestamps,
bytes[] calldata signatures
)
```
2. **Propose Mint**: Minters request to mint a specific amount to a destination, creating a proposal that validators can
review
```solidity
function proposeMint(uint256 amount, address destination)
```
3. **Execute Mint**: After a mandatory delay period (`mintDelay`) and before expiry (`mintTTL`), minters execute their
proposal if it hasn't been frozen or canceled by validators
```solidity
function mintM(uint256 mintId)
```
This multi-step approach with time delays creates security checkpoints where validators can intervene if they detect
suspicious activity, providing crucial protection for the protocol's monetary integrity.
#### Debt and Interest System
Minters accrue interest on their outstanding debt:
* **Continuous Indexing**: The same indexing system used in MToken tracks growth of obligations
* **Principal Storage**: Like earning balances, minter debt is stored as principal amounts that grow with interest
* **Rate Model**: Interest rates are determined by an external rate model set through governance
* **ActiveOwedM**: The current value of a minter's obligations including accrued interest
#### Validator Signature Verification
The signature verification system includes several security features:
* **Multi-signature Requirement**: Requires multiple validator signatures for collateral updates
* **Ordered Validator Addresses**: Validators addresses must be provided in ascending order to prevent duplicates
* **Timestamp Validation**: Each signature includes a timestamp that must be:
* Newer than the last signature from that validator for that minter
* Not in the future
* Not zero
* **Replay Protection**: The system tracks the last used timestamp for each validator-minter pair
* **Signature Format**: Uses EIP-712 typed data signatures that are validator-specific
#### Penalty System
The protocol enforces discipline through two types of penalties:
1. **Missed Updates Penalty**: Applied when minters fail to update collateral within required intervals
* Calculated as `penaltyRate × principalOfActiveOwedM × missedIntervals`
* Charged only once per missed interval
2. **Undercollateralization Penalty**: Applied when active owed $M exceeds allowed maximum
* Calculated as `penaltyRate × principalOfExcessOwedM × timeSpan / updateCollateralInterval`
* Proportional to the duration of undercollateralization
#### Collateral Retrievals
Minters can reduce their collateral through a structured process:
1. **Propose Retrieval**: Minter requests to retrieve a specific amount of collateral
```solidity
function proposeRetrieval(uint256 collateral)
```
2. **Validator Inclusion**: Validators specifically include the retrieval IDs they've approved in their next collateral
update signatures. Only these explicitly included retrievals will be processed.
3. **Retrieval Resolution**: When collateral is updated, the approved retrievals are processed and their amounts are no
longer deducted from the minter's effective collateral value
This ensures that collateral can only be retrieved with explicit validator approval, maintaining the system's safety.
Note the retrieval flow includes these technical aspects:
* Retrieval proposals have unique IDs generated sequentially
* Proposed retrievals are tracked in `_pendingCollateralRetrievals` mapping
* The system prevents retrievals that would cause undercollateralization
* Pending retrievals are explicitly deducted from the minter's effective collateral value for all system calculations,
ensuring proper collateralization before the retrievals are resolved
* Multiple retrievals can be resolved in a single update
* Retrievals can only be resolved during a validated collateral update, requiring validator signatures that explicitly
approve the retrieval IDs to be processed
### Validator Role
Validators serve as trusted entities who:
* Verify offchain collateral and sign update requests
* Must provide timestamps with their signatures to prevent replay attacks
* Can cancel suspicious mint proposals
* Can freeze minters to prevent further minting for a governance-defined period
* Enable the bridge between offchain assets and onchain accounting
### Burning Mechanism
Any token holder can burn MTokens to reduce a minter's debt:
* For active minters, burns reduce the principal of their owed M
* For deactivated minters, burns reduce their fixed inactive owed M
* After deactivation, burning serves as an informal liquidation mechanism
### Index Update Triggers
The interest index is updated in several key situations:
* When collateral is updated through `updateCollateral()`
* When tokens are minted via `mintM()`
* When tokens are burned via `burnM()`
* When a minter is deactivated via `deactivateMinter()`
* When `updateIndex()` is explicitly called
Each update synchronizes both the MinterGateway's index and the MToken's index simultaneously, ensuring that minter
obligation rates and earner yield rates maintain proper system balance throughout the protocol.
```solidity
function updateIndex() public override(IContinuousIndexing, ContinuousIndexing) returns (uint128 index_) {
// ...
index_ = super.updateIndex(); // Update minter index and rate.
// ...
IMToken(mToken).updateIndex(); // Update earning index and rate.
}
```
### Total Owed $M Accounting
The system tracks minter obligations with specialized accounting:
* **Active Owed M**: Tracked as principal amounts that accrue interest
* **Inactive Owed M**: Fixed amounts for deactivated minters that don't accrue interest
* **Total Owed M**: The sum of active and inactive obligations
* **Excess Owed M**: The difference between total owed $M and total $M supply, minted to the
[TTG Vault](/home/technical-documentations/distribution-vault/)
### Timing Constraints
The system enforces several timing-related constraints:
* **Collateral Update Frequency**: Minters must update at least once per `updateCollateralInterval`
* **Mint Delay**: Proposals must wait `mintDelay` seconds before execution
* **Mint TTL**: Proposals expire after `mintTTL` seconds
* **Earliest Collateral Update**: The timestamp for a new update must be greater than:
* The last update timestamp
* The latest proposed retrieval timestamp
* `block.timestamp - updateCollateralInterval`
### Security Mechanisms
The MinterGateway implements several safety features:
* **Mint Delay & TTL (Time-To-Leave)**: Creates a time window for validators to review and potentially cancel mints
* **Collateralization Caps**: Enforces maximum mint-to-collateral ratios (set by governance)
* **Multi-signature Requirement**: Requires multiple validator signatures for collateral updates
* **Strictly Ordered Validations**: Validator addresses must be provided in ascending order
* **Timestamp Verification**: Prevents reuse of signatures and ensures freshness of collateral data
* **Excess Yield Distribution**: Any yield generated by minters beyond what's distributed to earners goes to the TTG
Vault (also called the Distribution Vault)
### Emergency Measures
For extreme circumstances, the protocol includes additional safeguards:
#### Mint Proposal Cancellation
Validators can cancel suspicious mint proposals before they're executed, allowing them to quickly intervene if they
detect potentially harmful minting activity.
```solidity
function cancelMint(address minter_, uint256 mintId_) external onlyApprovedValidator {
uint48 id_ = _mintProposals[minter_].id;
if (id_ != mintId_ || id_ == 0) revert InvalidMintProposal();
delete _mintProposals[minter_];
emit MintCanceled(id_, minter_, msg.sender);
}
```
#### Validator Freeze
Validators can freeze minters to stop them from minting tokens for a governance-defined period:
```solidity
function freezeMinter(address minter_) external onlyApprovedValidator returns (uint40 frozenUntil_) {
unchecked {
_minterStates[minter_].frozenUntilTimestamp = frozenUntil_ = uint40(block.timestamp) + minterFreezeTime();
}
emit MinterFrozen(minter_, frozenUntil_);
}
```
#### Minter Deactivation
Governance can completely deactivate minters who have been removed from the approved list:
```solidity
function deactivateMinter(address minter_) external onlyActiveMinter(minter_) returns (uint240 inactiveOwedM_) {
if (isMinterApproved(minter_)) revert StillApprovedMinter();
// Implementation details for deactivation process
}
```
Once deactivated, a minter cannot be reactivated, providing a permanent resolution for problematic minters.
#### Open Redemption
Anyone can burn $M tokens to repay a deactivated minter's debt:
```solidity
function _repayForDeactivatedMinter(address minter_, uint240 maxAmount_) internal returns (uint240 amount_) {
amount_ = UIntMath.min240(inactiveOwedMOf(minter_), maxAmount_);
unchecked {
_rawOwedM[minter_] -= amount_;
totalInactiveOwedM -= amount_;
}
}
```
This creates an informal liquidation mechanism for deactivated minters.
### Validator Roles & Security Controls
Validators are independent, trusted entities that form a critical security layer for the minting and burning processes within the `MinterGateway`. Their responsibilities and powers include:
#### Core Responsibilities
* **Collateral Verification**: Validators verify Minters' offchain collateral and provide cryptographic signatures for `updateCollateral` transactions. These signatures attest to the value and existence of the assets backing $M.
* **Timestamping**: Signatures from Validators must include a secure timestamp to prevent replay attacks and ensure the freshness of collateral data.
* **Enabling Offchain-Onchain Bridge**: Validators act as the crucial link ensuring the onchain representation of collateral accurately reflects the state of offchain assets.
#### Security Controls & Emergency Powers
Validators are empowered with specific functions to intervene and protect the protocol:
1. **Cancel Mint Proposal**: If Validators deem a pending mint proposal to be suspicious, invalid, or potentially harmful to the protocol, they can cancel it before execution:
```solidity
function cancelMint(address minter, uint256 mintId) external onlyApprovedValidator;
```
This action deletes the mint proposal, preventing the Minter from executing it.
2. **Freeze Minter**: Validators can temporarily suspend a Minter's ability to mint new $M tokens:
```solidity
function freezeMinter(address minter) external onlyApprovedValidator returns (uint40 frozenUntil_);
```
This "freezes" the Minter for a duration specified by the governance parameter `MINTER_FREEZE_TIME`. A frozen Minter can still burn $M and update collateral but cannot propose or execute new mints until the freeze period expires.
These controls allow Validators to act as a rapid response mechanism against potential threats or misbehavior related to $M issuance.
### Overview
The `MinterGateway` contract is the central component within the M0 Protocol responsible for orchestrating the entire lifecycle of `$M`. It governs the processes for minting and burning, manages the offchain collateral system that backs the supply, and tracks the debt obligations of Minters. This contract acts as the primary interface for Minters and Validators, ensuring that issuance is always securely collateralized and transparently managed according to protocol rules defined by M0 Governance.
Its key responsibilities include:
* Managing Minter activation, deactivation, and operational states.
* Overseeing the collateral update process, including verification of offchain assets by Validators.
* Enforcing the structured minting and burning procedures.
* Calculating and applying interest, debt, and penalties for Minters.
* Facilitating the collateral retrieval process for Minters.
* Synchronizing its interest index with the `MToken` contract to maintain system-wide consistency.
### Minting & Burning Process
The creation (minting) and destruction (burning) of $M tokens are strictly controlled by the `MinterGateway`.
#### Minting $M
The minting process is designed with security checkpoints involving a proposal, a delay, and execution:
1. **Update Collateral (Prerequisite)**: Before proposing a mint, a Minter must have an up-to-date and sufficient verified collateral value on record.
2. **Propose Mint**: An active Minter initiates a mint request by calling `proposeMint()`:
This creates a mint proposal with a unique `mintId_`. The proposal specifies the amount of $M to be minted and the recipient address. This step allows Validators to review pending mints.
```solidity
function proposeMint(uint256 amount, address destination) external returns (uint48 mintId_);
```
3. **Delay Period**: A mandatory delay period (`MINT_DELAY`), set by governance, must pass after a mint is proposed before it can be executed. This window allows Validators to intervene (e.g., cancel the mint or freeze the Minter) if they detect any issues.
4. **Execute Mint**: After the `MINT_DELAY` has elapsed, and before the proposal expires (within `MINT_TTL` - Time To Live, also set by governance), the Minter can execute the mint by calling `mintM()`:
This function mints the specified amount of $M to the destination address, provided the proposal is still valid, hasn't been canceled, the Minter isn't frozen, and the mint doesn't violate collateralization requirements.
```solidity
function mintM(uint256 mintId) external;
```
The `MINT_DELAY` and `MINT_TTL` mechanisms create a secure time window for oversight, preventing immediate, unreviewed mints and ensuring proposals don't remain executable indefinitely.
#### Burning $M
$M tokens can be burned to reduce a Minter's outstanding debt. Any $M holder can initiate a burn against a specific Minter's debt:
```solidity
function burnM(address minter, uint256 amount) external;
```
* **For Active Minters**: Burning $M reduces the principal of their interest-accruing debt (`activeOwedM`).
* **For Deactivated Minters**: Burning $M reduces their fixed `inactiveOwedM`. This serves as an informal liquidation mechanism, allowing the market to help settle the obligations of a Minter who has exited the system. The `_repayForDeactivatedMinter` internal function handles this logic.
### Debt, Interest, and Penalties for Minters
Minters incur debt for the $M tokens they mint and are charged interest on this debt. The `MinterGateway` manages this system.
#### Debt and Interest
* **Debt Accrual**: When a Minter mints $M, they incur an equivalent amount of debt.
* **Continuous Indexing**: Minter debt, similar to `MToken` earning balances, is tracked using a continuous indexing system. The debt is stored as a principal amount that grows over time as interest accrues.
* **Interest Rate Model**: The interest rate charged to Minters (`MINTER_RATE_MODEL`) is determined by an external smart contract, the address of which is set by M0 Governance and stored in the `TTGRegistrar`.
* **`activeOwedM`**: This term represents the current total value of an active Minter's obligations, including all accrued interest. It is calculated by applying the current minter index to their principal debt.
#### Total Owed $M Accounting
The `MinterGateway` tracks various categories of Minter debt:
* **Active Owed $M**: The sum of interest-accruing debt from all active Minters.
* **Inactive Owed $M**: The sum of fixed, non-interest-accruing debt from all deactivated Minters.
* **Total Owed $M**: The aggregate of `activeOwedM` and `inactiveOwedM`.
* **Excess Owed $M**: The difference between `totalOwedM` (interest generated from Minters) and the total supply of $M (which grows at the earner rate). This excess, if positive, represents protocol revenue and is minted directly to the `DistributionVault` (TTGVault) during index updates.
#### Penalties
The protocol imposes penalties on Minters for failing to adhere to specific operational requirements, ensuring system discipline:
1. **Missed Collateral Updates Penalty**: If a Minter fails to update their collateral within the `UPDATE_COLLATERAL_INTERVAL`, a penalty is applied.
* Calculation: `PENALTY_RATE * principalOfActiveOwedM * missedIntervals`.
* This penalty is charged once per missed interval.
2. **Undercollateralization Penalty**: If a Minter's `activeOwedM` exceeds the maximum allowed by their verified collateral and the `MINT_RATIO`, they are penalized.
* Calculation: `PENALTY_RATE * principalOfExcessOwedM * (timeSpan / UPDATE_COLLATERAL_INTERVAL)`.
* This penalty is proportional to the amount and duration of the undercollateralization.
Penalties are added to the Minter's debt.
### Minter Lifecycle & Management
Minters are permissioned entities that interact with the `MinterGateway` to issue and manage $M. Their lifecycle within the protocol is characterized by several distinct statuses:
1. **Approved**: Minters are initially whitelisted by the M0 Two-Token Governance (TTG) system. At this stage, they are recognized by the protocol but cannot yet perform minting operations.
2. **Active**: An approved Minter must explicitly activate their status by calling `activateMinter()` on the `MinterGateway`.
```solidity
function activateMinter(address minter_) external;
```
This function can only be called by the Minter themselves and transitions them to an operational state, allowing them to propose collateral updates and mint $M. The active status is stored locally within `MinterGateway` for gas efficiency.
3. **Frozen**: An active Minter can be temporarily restricted from minting $M by Validators (via `freezeMinter()`). During this state, the Minter can still burn $M to reduce their debt and update their collateral but cannot propose new mints. The freeze duration is determined by governance.
4. **Deactivated**: A Minter can be deactivated if they are removed from the governance-approved list. Deactivation is a permanent state, initiated by a call to `deactivateMinter()`.
```solidity
function deactivateMinter(address minter_) external returns (uint240 inactiveOwedM_);
```
This function can be called if the Minter is no longer approved by TTG. Upon deactivation:
* The Minter's outstanding debt, including accrued interest, is converted into a fixed `inactiveOwedM` amount which no longer accrues interest.
* The Minter can no longer mint $M or propose collateral retrievals.
* Their collateral remains to back their `inactiveOwedM`.
* A deactivated Minter cannot be reactivated. This permanence optimizes gas by eliminating further status checks against the `TTGRegistrar`.
### Index Synchronization with $M token
The `MinterGateway` and the `MToken` contract both utilize a continuous indexing mechanism to track the accrual of interest – for Minter debt in `MinterGateway` and for earner balances in `MToken`. It is crucial that these indices are synchronized.
#### Synchronization Mechanism
The `MinterGateway` ensures that whenever its own interest index for Minter obligations is updated, the `MToken`'s earning index is also updated simultaneously. This is achieved within the `MinterGateway.updateIndex()` function:
```solidity
function updateIndex() public override(IContinuousIndexing, ContinuousIndexing) returns (uint128 index_) {
uint240 excessOwedM_ = excessOwedM();
if (excessOwedM_ > 0) IMToken(mToken).mint(ttgVault, excessOwedM_); // Mint excess to TTG Vault
// First, update the MinterGateway's own index and rate (for minter debt)
index_ = super.updateIndex();
// Then, explicitly trigger an update of the MToken's index and rate (for earner yield)
IMToken(mToken).updateIndex();
// Emit an event if this MinterGateway instance is also the MToken's MinterGateway
// (This is relevant if MToken directly calls this contract for its own updates)
if (address(this) == IMToken(mToken).minterGateway()) {
emit MTokenIndexUpdated(IMToken(mToken).currentIndex(), IMToken(mToken).currentRate());
}
}
```
This synchronized update ensures that the calculation of interest paid by Minters and yield distributed to Earners remains consistent and balanced across the protocol, reflecting the latest rates and system state.
#### Triggers for Index Updates
The `updateIndex()` function in `MinterGateway` (which in turn calls `MToken.updateIndex()`) is invoked during several key operations, ensuring indices are frequently updated:
* When a Minter updates their collateral via `updateCollateral()`.
* When $M tokens are minted via `mintM()`.
* When $M tokens are burned via `burnM()`.
* When a Minter is deactivated via `deactivateMinter()`.
* When `MinterGateway.updateIndex()` is explicitly called by an external party.
This frequent synchronization is vital for the accurate accounting of debts, yields, and the overall economic stability of the M0 Protocol.
### Collateral System
$M is backed by offchain collateral, typically real-world assets like U.S. Treasury Bills held in Special Purpose Vehicles (SPVs). The `MinterGateway` manages the onchain representation and verification of this collateral.
#### Offchain Backing & Onchain Representation
Minters are responsible for maintaining sufficient eligible collateral offchain. The value of this collateral is reported onchain through the `MinterGateway`.
#### Validator Verification
Trusted, independent Validators play a crucial role in verifying the Minters' offchain collateral. They periodically assess the collateral and provide cryptographic signatures attesting to its value.
#### Update Process
Minters must regularly update their onchain collateral value to reflect the current state of their offchain reserves. This is done by calling the `updateCollateral` function:
```solidity
function updateCollateral(
uint256 collateral,
uint256[] calldata retrievalIds,
bytes32 metadataHash,
address[] calldata validators,
uint256[] calldata timestamps,
bytes[] calldata signatures
) external returns (uint40 minTimestamp_);
```
Key aspects of the collateral update process:
* **Frequency**: Minters must update their collateral within a governance-defined interval (e.g., `UPDATE_COLLATERAL_INTERVAL`, typically daily). Failure to do so results in their onchain collateral value being considered zero until a new valid update is provided.
* **Validator Signatures**: A collateral update requires signatures from multiple approved Validators, meeting a minimum threshold set by governance (e.g., `UPDATE_COLLATERAL_THRESHOLD`).
* Signatures must be provided with validator addresses in ascending order to prevent duplicates.
* Each signature includes a timestamp which must be newer than the last signature from that specific validator for that Minter and must not be in the future. This prevents replay attacks.
* Signatures use EIP-712 typed data specific to each validator.
* **Collateral Expiration**: If a Minter fails to update their collateral within the `UPDATE_COLLATERAL_INTERVAL`, their effective onchain collateral value is treated as zero for all protocol calculations, potentially leading to penalties or an inability to mint.
* **Collateralization Caps**: Minters must maintain a collateralization ratio above the governance-set `MINT_RATIO`. This ratio dictates the maximum amount of $M a Minter can have outstanding relative to their verified collateral.
* **Earliest Update Timing**: A new collateral update's effective timestamp must be greater than:
* The Minter's last update timestamp.
* The timestamp of the Minter's latest proposed collateral retrieval.
* `block.timestamp - UPDATE_COLLATERAL_INTERVAL` to ensure updates are reasonably current.
### Collateral Retrieval Process
Minters can retrieve excess collateral from their backing SPV, provided the retrieval does not compromise their ability to back their outstanding $M debt. This process is managed onchain through the `MinterGateway` and requires Validator oversight.
1. **Propose Retrieval**: The Minter initiates a request to retrieve a specific amount of collateral by calling `proposeRetrieval()`:
```solidity
function proposeRetrieval(uint256 collateral) external returns (uint48 retrievalId_);
```
This creates a pending retrieval proposal with a unique, sequential `retrievalId_`. The proposed retrieval amount is tracked in the `_pendingCollateralRetrievals` mapping and is immediately deducted from the Minter's effective collateral value for all ongoing calculations, ensuring the Minter remains adequately collateralized. The system prevents proposals that would lead to undercollateralization.
2. **Validator Inclusion & Resolution**: For a pending retrieval to be finalized, it must be explicitly included and approved by Validators in a subsequent `updateCollateral` transaction.
* When Validators sign off on a Minter's collateral update, their signature digest includes the list of `retrievalIds` they are approving for resolution.
* During the `updateCollateral` call, the `MinterGateway` processes these approved `retrievalIds`. The corresponding amounts are then considered formally retrieved, and their values are no longer deducted from the Minter's available collateral.
This multi-step process ensures that collateral can only be effectively retrieved with explicit, signed approval from Validators as part of a standard, verified collateral update, maintaining the integrity of $M's backing. Multiple retrieval proposals can be resolved within a single `updateCollateral` transaction.
### Technical Mechanics
#### Dual Balance System
The dual balance system is one of the core innovations of the $M token, enabling it to function both as a standard stable token and a yield-bearing asset.
##### Two Balance Types
| Non-Earning | Earning (allowed by governance) |
| ---------------------------------------------------- | ------------------------------------------------------------------------------- |
| Function exactly like regular ERC20 tokens | Automatically increase in value over time through continuous compounding |
| Value remains constant unless explicitly transferred | Stored internally as "principal amounts" that get multiplied by a growing index |
| Stored as actual token amounts in the contract | Interest accrues without requiring any transactions or claim process |
#### Balance Conversions in $M token
The `MToken` contract implements a dual accounting system that allows users and developers to leverage both non-earning and earning states. Understanding how the state changes work is crucial.
##### Non-earning to Earning Conversion Process
When an approved user calls `startEarning()` to convert from non-earning to earning status:
1. The contract first checks if the user is approved as an earner in the M0 Governance system (via the `TTGRegistrar`).
2. The user's current token balance (let's call it `amount`) is read from storage.
3. This `amount` is converted to a smaller "principal amount" using the following calculation:
```math
principalAmount = \frac{amount}{currentIndex}
```
where `currentIndex` started at 1.0 (represented as 1e12) and has been growing continuously based on the `EARNER_RATE_MODEL`.
4. The conversion rounds **down** in favor of the protocol (a tiny fraction may be left, contributing to protocol reserves).
5. The user's raw balance in storage (within the `_balances` mapping, specifically `MBalance.rawBalance`) is updated to this `principalAmount`. Their `MBalance.isEarning` flag is set to `true`.
6. Global accounting variables are updated:
* `totalNonEarningSupply` is decreased by the original `amount`.
* `principalOfTotalEarningSupply` is increased by the `principalAmount`.
This `principalAmount` will continually grow in value as the `currentIndex` increases. The user doesn't see their token count change directly in the `rawBalance` storage, but when they check their balance through `balanceOf()`, the contract multiplies their `principalAmount` by the `currentIndex` to show their true balance including all earned interest.
##### Earning to Non-earning Conversion Process
When a user calls `stopEarning()` to convert from earning to non-earning status:
1. The contract reads the user's current `principalAmount` from storage.
2. This principal is converted back to a "present amount" (actual token value) by multiplying:
```math
presentAmount = principalAmount \times currentIndex
```
3. The resulting `presentAmount` includes all interest earned up to that moment. The calculation of `presentAmount` from `principalAmount` is rounded **down** (`_getPresentAmountRoundedDown`).
4. The user's `rawBalance` in storage is updated to this `presentAmount`. Their `MBalance.isEarning` flag is set to `false`.
5. Global accounting variables are updated:
* `totalNonEarningSupply` is increased by the `presentAmount`.
* `principalOfTotalEarningSupply` is decreased by the `principalAmount`.
After this conversion, the user's balance no longer earns interest but now includes all interest accrued up to the conversion point. Their balance will remain static until they perform another transaction or start earning again (if still approved).
##### Example to Illustrate
Consider a user with 1,000 $M tokens in non-earning state, and the `currentIndex` is 1.05 (representing a 5% increase since inception):
1. When they call `startEarning()`:
* `principalAmount = 1000 / 1.05 \approx 952.380952` (actual value depends on 6 decimals for $M and 12 decimals for index, then rounded down). Let's say it's `952.380952 * 10^6` as raw principal.
* Their storage value for `rawBalance` becomes this principal.
* `balanceOf()` returns `(principalAmount * 1.05 * 10^12) / 10^{12} \approx 1000 * 10^6`.
2. After time passes and the `currentIndex` grows to 1.08:
* Their stored `rawBalance` (principal) is still `952.380952 * 10^6`.
* `balanceOf()` now returns `(principalAmount * 1.08 * 10^12) / 10^{12} \approx 1028.571428 * 10^6`.
3. When they call `stopEarning()`:
* `presentAmount = (principalAmount * 1.08 * 10^{12}) / 10^{12}` (rounded down).
* Their storage value for `rawBalance` becomes this `presentAmount`.
* `balanceOf()` now returns exactly this `presentAmount`.
#### Transfer Mechanics
Transfers in `MToken` handle accounts with different earning statuses:
1. **In-kind Transfers**: Between two accounts with the same earning status.
* Between two earning accounts: The transfer involves principal amounts. The amount to be transferred is converted to its principal equivalent (rounded **up** if sender, effectively taking slightly more principal for the given token amount) and then adjusted in the sender's and receiver's principal balances.
* Between two non-earning accounts: The transfer is a standard token amount transfer.
2. **Out-of-kind Transfers**: Between accounts with different earning statuses.
* From earning to non-earning: The sender's earning balance (principal) is reduced (principal equivalent rounded **up**), and the receiver's non-earning balance (token amount) is increased by the specified token amount.
* From non-earning to earning: The sender's non-earning balance (token amount) is reduced, and the receiver's earning balance is increased by the principal equivalent of the token amount (rounded **down**).
#### Strategic Rounding
The protocol employs consistent rounding rules that slightly favor the protocol to create a small buffer, enhancing system stability and protecting against potential exploitation. These small rounding differences accumulate as protocol reserves.
| Operation | From | To | Rounding Rule for Amount Conversion to Principal | Protocol Favored | Internal Function Involved (Illustrative) |
| :------------------- | :--------- | :--------- | :--------------------------------------------------------------------------------------------- | :--------------- | :------------------------------------------ |
| **Conversions** | | | | | |
| Start Earning | Non-Earner | Earner | Principal from present amount rounded **DOWN** | Yes | `_getPrincipalAmountRoundedDown` |
| Stop Earning | Earner | Non-Earner | Present amount from principal rounded **DOWN** (for the final balance calculation) | Yes | `_getPresentAmountRoundedDown` |
| **Transfers** | | | | | |
| Earner to Earner | Earner | Earner | Amount to principal for sender: rounded **UP**; for receiver: (principal transferred directly) | Sender: Yes | `_getPrincipalAmountRoundedUp` (sender) |
| Earner to Non-Earner | Earner | Non-Earner | Amount to principal for sender: rounded **UP** | Yes | `_getPrincipalAmountRoundedUp` (sender) |
| Non-Earner to Earner | Non-Earner | Earner | Amount to principal for receiver: rounded **DOWN** | Yes | `_getPrincipalAmountRoundedDown` (receiver) |
| **Mint/Burn** | | | | | |
| Mint to Earner | - | Earner | Amount to principal rounded **DOWN** | Yes | `_getPrincipalAmountRoundedDown` |
| Burn from Earner | Earner | - | Amount to principal rounded **UP** | Yes | `_getPrincipalAmountRoundedUp` |
*(Note: The table simplifies complex interactions. Refer to the [`MToken.sol`](/home/technical-specifications/m-token/) contract for precise implementation details, especially `_transferOutOfKind`, `_transferAmountInKind`, `_addEarningAmount`, `_subtractEarningAmount`, `_addNonEarningAmount`, `_subtractNonEarningAmount`.)*
#### Global Index & Continuous Compounding
At the heart of the $M token's interest mechanism for earning balances is a global index that efficiently tracks the growth of all earning balances through continuous compound interest.
##### Index Mechanism
* **Single Global Index**: A single shared growth factor (`latestIndex`) applies to all earning balances, making the system highly gas-efficient.
* **Mathematical Relationship**: For an earning account, its `balanceOf()` is effectively `principalAmount * currentIndex`.
* **Starting Point**: The index starts at 1.0 (represented as `1e12` internally due to 12 decimal places of precision for the index) and only increases over time.
* **Index Storage**: The `MToken` contract inherits from `ContinuousIndexing`, which stores `latestIndex` (uint128) and `latestUpdateTimestamp` (uint40).
##### Mathematical Implementation
The `MToken` contract implements true continuous compounding:
* **Continuous Compounding Formula**: The index is updated using the formula `newIndex = oldIndex * e^(rate * timeElapsed / SECONDS_PER_YEAR)`.
* **Interest Accumulation**: Interest compounds with every second that passes, with the `currentIndex()` function calculating the up-to-date index on-demand.
* **Exponential Approximation**: The `e^x` function is implemented onchain using a Padé approximant R(4,4) for gas efficiency and precision:
```math
e(x) \approx \frac{1 + \frac{x}{2} + \frac{3x^2}{28} + \frac{x^3}{84} + \frac{x^4}{1680}}{1 - \frac{x}{2} + \frac{3x^2}{28} - \frac{x^3}{84} + \frac{x^4}{1680}}
```
* **Rate Conversion**: Rates obtained from the `EARNER_RATE_MODEL` (in basis points) are converted to a scaled format suitable for the exponential calculations.
* **Time Scaling**: Rates are scaled by the time elapsed (in seconds) divided by `SECONDS_PER_YEAR` (31,536,000).
##### Principal vs. Present Value
The contract manages two key value concepts for earning balances:
* **Principal Amount**: The base `rawBalance` (uint240, but effectively uint112 for principal part) stored for earning accounts. This is the amount that earns interest.
* **Present Amount**: The current value of an earning balance, including all accrued interest, calculated as `principalAmount * currentIndex`. This is what `balanceOf()` returns.
Conversion functions handle translations:
* `_getPresentAmount(uint112 principalAmount_)` or `_getPresentAmountRoundedDown(uint112 principalAmount, uint128 index_)`: Multiplies principal by the current index.
* `_getPrincipalAmountRoundedDown(uint240 presentAmount_)`: Divides present amount by the current index, rounded down. Used when adding to earning accounts.
* `_getPrincipalAmountRoundedUp(uint240 presentAmount_)`: Divides present amount by the current index, rounded up. Used when subtracting from earning accounts.
##### Index Updates
The global earning index (`latestIndex`) in `MToken` is updated by calling `updateIndex()`. This function is typically called:
* By the `MinterGateway` during its own `updateIndex()` calls (e.g., during `mintM`, `burnM`, `updateCollateral`, `deactivateMinter`). This ensures synchronization between Minter interest payments and Earner yield accrual.
* Internally within `MToken` before operations that change an account's earning status or modify `principalOfTotalEarningSupply`, such as:
* `_startEarning()`
* `_stopEarning()`
* Minting directly to an earning account via `_mint()`.
* Burning directly from an earning account via `_burn()`.
* Transfers that involve an earning account (`_transferOutOfKind`, `_transferAmountInKind` for earning-to-earning).
Each `MToken.updateIndex()` call:
1. Fetches the current earner rate from the `EARNER_RATE_MODEL` (address obtained from `TTGRegistrar`).
2. Calculates the time elapsed since `latestUpdateTimestamp`.
3. Computes the new index: `newIndex = latestIndex * e^(rate * timeElapsed / SECONDS_PER_YEAR)`.
4. Updates `latestIndex` and `latestUpdateTimestamp` in storage.
##### Precision and Efficiency
* **Fixed-Point Arithmetic**: The index uses 12 decimal places of precision (scaled by `1e12`). $M balances have 6 decimals.
* **Conservative Rounding**: All rounding strategies are designed to favor protocol safety, with small residuals contributing to protocol reserves.
* **Gas Optimization**: The single global index is updated only when necessary and is shared across all earning accounts.
* **Overflow Protection**: The index is capped at `type(uint128).max`. Principal amounts are also managed to prevent overflow in calculations.
### Security & Governance Interaction
The security and behavior of the $M token are deeply intertwined with the M0 Protocol's governance and other core contracts.
#### Supply Control
* The total supply of $M is exclusively controlled by the `MinterGateway` contract.
* `MToken.mint(address recipient, uint240 amount)` can only be called by the `minterGateway` address set in `MToken`.
* `MToken.burn(address account, uint240 amount)` can only be called by the `minterGateway` address.
This ensures that $M tokens are only created or destroyed as per the protocol's minting (collateral-backed) and burning (debt-repayment) rules managed by `MinterGateway`.
#### Earning Mechanism Governance
* **Earner Approval**: Whether an account can switch to an earning balance (`startEarning()`) is determined by M0 Governance. The `MToken` contract checks if an account is an approved earner by querying the `TTGRegistrar` (specifically, the `APPROVED_EARNERS` list or if `EARNERS_LIST_IGNORED` is true).
```solidity
// Simplified check from _isApprovedEarner(address account_)
function _isApprovedEarner(address account_) internal view returns (bool) {
return ttgRegistrarReader.getBool("earners_list_ignored") || ttgRegistrarReader.listContains("earners", account_);
}
```
* **Rate Model Control**: The interest rate for earners is determined by the `EARNER_RATE_MODEL`. The address of this model is a governance-controlled parameter stored in the `TTGRegistrar` and read by `MToken` during `updateIndex()`.
#### Safety Controls for Earners
* The `stopEarning(address account_)` function allows anyone to call it for an `account_` that is *no longer* on the `APPROVED_EARNERS` list. This is a crucial safety mechanism to prevent an account from continuing to accrue yield if its earner status is revoked by governance.
```solidity
function stopEarning(address account_) external {
if (_isApprovedEarner(account_)) revert IsApprovedEarner(); // Can only be called if account is NOT an approved earner
_stopEarning(account_);
}
```
#### Index Management and Synchronization
* The `MToken.updateIndex()` function is critical for reflecting the correct yield. As mentioned, it's called by `MinterGateway` to ensure synchronization with Minter-side interest accruals and by `MToken` itself during critical state changes. This synchronization is vital for the protocol's economic balance.
#### Rounding and Numerical Stability
* The consistent use of protocol-favoring rounding rules in conversions and transfers contributes to the robustness of the $M token system. These small, accumulated amounts act as a buffer and eventually contribute to protocol reserves (e.g., claimable by the `DistributionVault`).
* Explicit overflow checks and the use of libraries like `UIntMath` and `ContinuousIndexingMath` ensure numerical precision and prevent errors during balance and index calculations.
#### Immutability
The `MToken` contract itself is designed to be immutable, meaning its core logic cannot be upgraded directly. Changes to its behavior (like interest rates or earner eligibility) are managed externally through governance-controlled parameters in the `TTGRegistrar` and updatable Rate Model contracts.
import ZoomableImage from "@/components/ZoomableImage";
### Overview
*M is for Money.*
`$M` is an immutable ERC20-compliant token at the heart of the M0 Ecosystem. It serves as the foundational building block for all M0 Extensions. Each unit is backed 1:1 by approved collateral. It implements an innovative dual-balance accounting system:
1. **Non-earning Balances**: Standard ERC20-style token balances that remain static over time — non-rebasing amounts do not increase without external transactions.
2. **Earning Balances**: Token balances that automatically increase in quantity over time through continuous compounding. Holders with earning balances receive additional tokens without requiring any transactions. This rebasing mechanism means users see their token count grow as interest accrues continuously, with their viewable balance updating in real-time (per block).
This design separates standard token functionality from interest-bearing capabilities. The M0 Two-Token Governance (TTG) system determines which accounts are eligible to become Earners through an approval process recorded in the [`TTGRegistrar`](/home/technical-documentations/ttg-governance/overview/).
`$M` interacts closely with several **key components** of the M0 Protocol:
* **[`MinterGateway`](/home/technical-documentations/mintergateway/overview/)**: Controls the total supply through secure mint and burn operations.
* **[`Rate Models`](/home/technical-documentations/rate-models/)**: External smart contracts that dynamically calculate the interest rates for both Minters and Earners.
* **[`TTGRegistrar`](/home/technical-documentations/ttg-governance/overview/)**: The governance-controlled parameter store that, among other things, maintains the list of approved Earners and the addresses of the active Rate Models.
* **[`DistributionVault`](/home/technical-documentations/distribution-vault/)**: Receives excess yield, which arises when the interest charged to Minters exceeds the yield distributed to Earners.
### Key Features and User Interactions
* **Governance-Approved Yield Access**: Selected accounts can earn continuous yield after Two Token Governance (TTG) approval.
* **Automatic Yield**: Earning balances grow continuously without requiring any user action.
* **Transparency**: All interest calculations occur onchain with fully transparent mechanics.
* **Efficiency**: A single token contract handles both stable (non-earning) and yield-generating (earning) use cases.
* **Precision**: Advanced mathematical techniques are employed to ensure accurate interest calculations.
* **Safety Mechanisms**: Protections are in place to ensure accounts removed from the `APPROVED_EARNERS` list by governance cannot continue earning.
#### User Interactions
##### Basic Token Operations
`$M` implements all standard ERC20 functions and includes additional features:
* It has **6 decimal places** of precision for all operations.
* Supports standard transfers (`transfer`, `transferFrom`), approvals (`approve`), and balance inquiries (`balanceOf`).
* Includes support for EIP-2612 `permit` for gasless approvals.
* Includes support for EIP-3009 for transfers with authorization (`transferWithAuthorization`, `receiveWithAuthorization`).
##### Earning Status Management
Users can interact with the earning functionality through several key functions on the `MToken` contract:
* `startEarning()`: Converts an account's regular (non-earning) balance to an earning balance. This function can only be called by an account for itself, and only if that account has been approved as an Earner by M0 Governance.
* `stopEarning()`: Converts an account's earning balance back to a regular (non-earning) balance. The account retains all interest accrued up to that point. This can be called by the account owner.
* `stopEarning(address account_)`: A safety function that allows anyone to stop the earning status for a specified `account_` if that account is no longer on the governance-approved `APPROVED_EARNERS` list. This ensures that accounts cannot continue to earn yield if their approval is revoked.
##### Balance Querying
Several methods are available to check different aspects of balances:
* `balanceOf(address account)`: Returns the current balance of the account. For earning accounts, this includes all accrued interest up to the current block.
* `principalBalanceOf(address account)`: Returns the underlying principal amount for earning accounts. For non-earning accounts, this will be 0.
* `isEarning(address account)`: Checks if an account is currently in earning mode.
* `totalEarningSupply()`: Returns the total amount of tokens (present value including accrued interest) currently held in earning balances.
* `totalNonEarningSupply()`: Returns the total amount of tokens held in non-earning balances.
The `totalSupply()` is the sum of `totalEarningSupply()` and `totalNonEarningSupply()`.
## Underlying Technology
### Wormhole NTT (M Portal)
M Portal (Standard) is built *using* the Wormhole Native Token Transfer (NTT) framework.
* **Integration:** `HubPortal` and `SpokePortal` inherit from Wormhole's `NttManager` abstract contract. This provides the base functionality for interacting with Wormhole Transceivers, encoding/decoding messages, and handling standard NTT flows.
* **Customization:** M0 extends this base functionality to:
* Implement the specific Lock/Release (Hub) and Mint/Burn (Spoke) logic.
* Handle the automatic unwrap/wrap mechanism for $M extensions.
* Inject the M0-specific metadata (like `$M Earning Index`) into the `additionalPayload` field of standard NTT messages.
* Define and handle custom message types for explicit metadata propagation.
* **Abstraction:** Using the NTT framework allows M Portals to leverage Wormhole's established messaging infrastructure while focusing on M0-specific logic.
### Hyperlane (M Portal Lite)
M Portal Lite uses Hyperlane as its messaging layer and features a more modular design.
* **Integration:** The `Portal` contracts do **not** inherit from a bridge-specific contract. Instead, they hold an address of a contract that implements the generic `IBridge` interface. The `HyperlaneBridge.sol` contract is the concrete implementation that connects the Portals to the Hyperlane protocol.
* **Decoupling:** This design decouples the token bridging logic from the cross-chain messaging logic. The Portals are only aware of the `IBridge` interface, making the system potentially adaptable to other messaging bridges in the future.
* **Message Flow:**
1. A `Portal` contract calls `sendMessage()` on the configured `HyperlaneBridge`.
2. `HyperlaneBridge` formats the message and dispatches it through the Hyperlane `IMailbox` contract.
3. On the destination chain, the Hyperlane network delivers the message to the destination `HyperlaneBridge` by calling its `handle()` function.
4. The destination `HyperlaneBridge` authenticates the message (verifying it comes from the Mailbox and a known peer) and then calls `receiveMessage()` on its local `Portal` contract, delivering the payload.
import ZoomableImage from "@/components/ZoomableImage";
## M0 Cross-Chain Architecture: M Portals
### Overview
#### Native Multichain $M
The core objective of M0's multichain strategy is to make the native $M token accessible across various blockchains beyond Ethereum mainnet. This aims to provide users with a consistent experience, particularly regarding yield-earning capabilities, regardless of the chain they operate on, while maintaining Ethereum as the authoritative source for $M issuance and governance.
**M Portals** are the set of smart contracts that facilitate this cross-chain functionality. They are responsible for bridging the $M token itself, as well as propagating essential system information like the yield index and governance parameters between Ethereum and connected Spoke chains.
To cater to different ecosystem needs and technical requirements, M0 offers two distinct implementations of the Portal system.
#### M Portal Implementations: Standard vs. Lite
M0 provides two portal versions built on different underlying technologies. While they share the same core goal of bridging $M and its metadata, they have key differences in architecture, features, and chain support.
1. **M Portal (Standard):** The original, feature-rich implementation built on the **Wormhole Native Token Transfer (NTT)** framework. It is designed for maximum compatibility, supporting both EVM and non-EVM chains (like Solana) and allowing for spoke-to-spoke transfers.
2. **M Portal Lite:** A streamlined, gas-efficient, and more modular implementation built on the **Hyperlane** messaging protocol. It is designed specifically for **EVM-only environments**, follows a stricter hub-and-spoke model (no spoke-to-spoke transfers), and introduces enhanced security mechanisms and a developer-friendly upgradeable design.
#### At a Glance: M Portal vs. M Portal Lite
| Feature | M Portal (Standard) | M Portal Lite |
| --------------------- | ---------------------------------- | ----------------------------------------------- |
| **Bridge Technology** | Wormhole NTT | Hyperlane |
| **Chain Support** | EVM & non-EVM (e.g., Solana) | EVM-only |
| **Communication** | Hub-Spoke & Spoke-Spoke | Strictly Hub-Spoke |
| **Contract Design** | Monolithic (inherits `NttManager`) | Modular & Extendable (uses `IBridge` interface) |
| **Security Model** | Hub balance = Total locked | Hub tracks `bridgedPrincipal` per spoke chain |
| **Chain IDs** | Wormhole `uint16` IDs | Standard EVM `uint256` IDs |
### The Hub-and-Spoke Model
Both M Portal implementations employ a Hub-and-Spoke architecture for multichain deployment:
* **Hub Chain (Ethereum):**
* The single source of truth for the native $M token (where it's initially minted and governed).
* Hosts the `HubPortal` contract.
* Token Mechanism: **Lock-and-Release**. When bridging *from* the Hub, $M tokens are locked. When bridging *back* to the Hub, these locked tokens are released.
* Acts as the source for propagating the $M Earning Index and TTG Registrar values.
* *(M Portal Lite Specific):* The `HubPortal` also tracks the principal amount of $M bridged to each spoke chain to ensure it never releases more tokens than were locked for that specific spoke.
* **Spoke Chains (e.g., Optimism, Arbitrum):**
* Host representations of the $M token.
* Host `SpokePortal` contracts.
* Token Mechanism: **Mint-and-Burn**. When bridging *to* a Spoke chain, a corresponding amount of $M representation is minted. When bridging *from* a Spoke chain, the $M representation is burned.
* Receive and apply the $M Earning Index and TTG Registrar values propagated from the Hub.
This model ensures that the canonical $M supply originates and is controlled solely on Ethereum, while Spoke chains manage representations backed by the locked tokens on the Hub.
## Metadata Propagation
Beyond token value, M Portals are crucial for synchronizing the M0 protocol state across chains. The purpose and data are the same for both implementations, but the transport mechanism differs.
* **Purpose:** To ensure yield calculations (`$M Earning Index`) and governance parameters (`TTG Registrar` values) are consistent between the Hub and all Spoke chains.
* **Propagated Data:**
* **$M Earning Index:** A continuously accruing value representing the aggregate yield earned within the M0 system on Ethereum. Propagating this allows the $M representation on Spoke chains to reflect the correct yield.
* **TTG Registrar Values:** Key-value pairs stored in the `Registrar` contract on Ethereum, set by M0 governance (TTG). Examples include the `EARNERS_LIST` which dictates which addresses are eligible for yield. Propagation ensures Spoke chains respect Hub governance decisions.
* **Propagation Mechanisms:**
* **Implicit (with token transfers):** Every token transfer message from the `HubPortal` automatically includes the current `$M Earning Index` in its payload. The receiving `SpokePortal` uses this to update its local state.
* **Explicit (on-demand):** Anyone can permissionlessly call functions on the `HubPortal` to send the latest metadata to a specific Spoke chain.
* `sendMTokenIndex(uint16 destinationChainId)`: Sends the current $M Index to a specific Spoke chain.
* `sendRegistrarKey(uint16 destinationChainId, bytes32 key)`: Sends the value associated with a specific key from the Hub `Registrar` to a Spoke `Registrar`.
* `sendRegistrarListStatus(uint16 destinationChainId, bytes32 listKey, address entry)`: Sends the inclusion status of a specific address within a list (like `EARNERS_LIST`) in the Hub `Registrar` to the Spoke `Registrar`.
* **Frequency:** Explicit updates are often triggered periodically by automated bots to ensure timely synchronization even without user transfer activity.
## Key Components
### Common Components
* **User:** The EOA or contract initiating or receiving cross-chain transfers.
* **$M Token:** The native ERC-20 compliant token contract deployed on Ethereum.
* **Wrapped $M / Extensions:** ERC-20 wrappers for $M that must implement `wrap` and `unwrap` functions.
* **`HubPortal` (Ethereum):** Manages locking/releasing of native $M and propagates metadata.
* **`SpokePortal` (Spoke Chains):** Manages minting/burning of the Spoke chain's $M representation and receives metadata.
### M Portal (Wormhole) Specific Components
* **Wormhole NTT Framework:** The underlying technology. `HubPortal` and `SpokePortal` inherit from its `NttManager` contract.
* **Wormhole Transceiver:** A Wormhole contract on each chain that `NttManager` uses to send/receive low-level messages.
* **Wormhole Core / Guardians / Relayers:** The fundamental Wormhole infrastructure that validates and delivers messages (VAAs).
* **`MerkleTreeBuilder.sol`:** A contract used on the Hub to generate Merkle roots for propagating large datasets (like earner lists) efficiently to non-EVM chains like Solana.
### M Portal Lite (Hyperlane) Specific Components
* **`IBridge.sol`:** A generic interface that decouples the Portal logic from the underlying bridge technology.
* **`HyperlaneBridge.sol`:** The implementation of `IBridge` that interacts with the Hyperlane protocol (`IMailbox`) to send and receive messages.
* **Hyperlane Mailbox / Relayers:** The core Hyperlane infrastructure for message dispatch, security, and delivery.
* **Upgradeable Contracts:** `HubPortal` and `SpokePortal` are built using the OpenZeppelin Upgrades pattern, allowing for proxy-based upgrades.
## Developer Integration & Security Considerations
* **Audits:** The M Portal contracts have undergone security audits. Refer to linked audit reports for findings and details.
* **Upgradeability:**
* **M Portal Lite:** Is designed as an upgradeable system using the UUPS proxy pattern (via OpenZeppelin Upgrades). This allows for seamless logic updates without requiring contract migration.
* **M Portal (Standard):** The contracts are also designed to be upgradeable.
* **Security Dependencies:**
* **M Portal (Wormhole):** Security relies significantly on the integrity of the Wormhole Guardians and the NTT framework contracts.
* **M Portal Lite (Hyperlane):** Security relies on the Hyperlane protocol, including its configured Interchain Security Modules (ISMs) and the correctness of the Hyperlane Relayer network.
* **Key Security Feature (Lite):** The `bridgedPrincipal` tracking in the `HubPortal` of M Portal Lite provides an additional layer of accounting security, ensuring that the Hub cannot be drained of more tokens than were originally locked for a given spoke chain.
* **Code Repositories:**
* M Portal (Wormhole): [https://github.com/m0-foundation/m-portal](https://github.com/m0-foundation/m-portal)
* M Portal Lite (Hyperlane): [https://github.com/m0-foundation/m-portal-lite](https://github.com/m0-foundation/m-portal-lite)
* **External Documentation:**
* Wormhole NTT Documentation: [https://docs.wormhole.com/wormhole/ntt/overview](https://docs.wormhole.com/wormhole/ntt/overview)
* Hyperlane Documentation: [https://docs.hyperlane.xyz/](https://docs.hyperlane.xyz/)
* **Audit Reports:**
* M Portal (Wormhole): [https://github.com/m0-foundation/m-portal/tree/main/audits](https://github.com/m0-foundation/m-portal/tree/main/audits)
* M Portal Lite (Hyperlane): [https://github.com/m0-foundation/m-portal-lite/tree/main/audits](https://github.com/m0-foundation/m-portal-lite/tree/main/audits)
import ZoomableImage from "@/components/ZoomableImage";
## Core Bridging Process
The transfer of $M (or its extensions like Wrapped $M) between the Hub and Spoke chains follows a similar logical flow in both implementations, orchestrated by the Portals and their underlying messaging bridge.
### Transfer: Hub Chain -> Spoke Chain
1. **User Interaction:** The user initiates a transfer by calling a function on the `HubPortal` contract on Ethereum, specifying the destination chain and amount. Beforehand, users can call a function to get the estimated delivery fee for the cross-chain transaction.
* **M Portal (Standard):** The function is `quoteDeliveryPrice`, inherited from the Wormhole NTT framework.
* **M Portal Lite:** The function is `quoteTransfer`.
After getting the fee and approving the `HubPortal` to spend their $M or Wrapped $M, the user makes the final transfer call.
2. **Token Handling:**
* If the user transferred Wrapped $M, the `HubPortal` first unwraps it to the native $M token.
* The native $M tokens are then **locked** within the `HubPortal` contract.
* *(M Portal Lite Specific):* The `HubPortal` increments its internal `bridgedPrincipal` counter for the destination spoke chain.
3. **Message Creation & Sending:**
* The `HubPortal` constructs a cross-chain message containing transfer details (amount, recipient, destination chain) and additional metadata (like the current $M Index).
* It interacts with its configured bridge to send this message.
* **M Portal (Wormhole):** Interacts with the Wormhole Transceiver via the `NttManager` logic to publish a message to the Wormhole network.
* **M Portal Lite (Hyperlane):** Interacts with the `HyperlaneBridge` contract, which dispatches the message via the Hyperlane Mailbox.
4. Cross-Chain Relaying (Offchain): The message is validated by the underlying bridge's security mechanism (Wormhole Guardians or Hyperlane's Interchain Security Modules (ISMs)). Once validated, the message is delivered by relayers to the destination Spoke chain.
5. **Message Reception & Processing:**
* The bridge contract on the Spoke chain receives the message and passes it to the `SpokePortal`.
6. **Token Minting & Delivery:**
* The `SpokePortal` processes the message.
* It **mints** the corresponding amount of the $M token representation on the Spoke chain.
* If the original transfer was for Wrapped $M, the `SpokePortal` wraps the newly minted $M.
* The final token is transferred to the recipient address on the Spoke chain.
### Transfer: Spoke Chain -> Hub Chain
This is largely the reverse process.
1. **User Interaction:** User calls the `SpokePortal` on a Spoke chain to send tokens back to the Hub.
2. **Token Handling:**
* If transferring Wrapped $M, it's unwrapped to the native $M representation.
* The native $M tokens are **burned** by the `SpokePortal`.
3. **Message Creation & Sending:** The `SpokePortal` sends a cross-chain message back to the Hub Chain (Ethereum).
4. **Cross-Chain Relaying:** The message is relayed back to Ethereum.
5. **Message Reception & Processing:** The bridge on Ethereum receives the message and passes it to the `HubPortal`.
6. **Token Releasing & Delivery:**
* The `HubPortal` processes the message and **releases** (unlocks) the equivalent amount of native $M tokens.
* *(M Portal Lite Specific):* The `HubPortal` first verifies that the requested release amount does not exceed the `bridgedPrincipal` for that source spoke chain. It then decrements the `bridgedPrincipal` counter.
* The released tokens (wrapped if necessary) are delivered to the recipient on Ethereum.
### Transfer: Spoke Chain -> Spoke Chain
* **M Portal (Wormhole):** **Supported.** This process involves burning tokens on the source Spoke and minting them on the destination Spoke, with a message relayed via Wormhole.
* **M Portal Lite (Hyperlane):** **Not Supported.** The system is strictly Hub-and-Spoke. To move assets between spokes, a user must first bridge back to the Hub (Ethereum) and then out to the new Spoke chain.
## M0 Extensions: Custom Stablecoins Built on the M0 Platform
### What Are M0 Extensions?
M0 Extensions are the **application layer** of the M0 Protocol. They are custom ERC-20 stablecoins that developers build on the M0 platform, inheriting its security and yield properties while adding their own unique features, branding, and business logic.
Think of M0 as the secure, collateralized foundation—like digital infrastructure. Extensions are the applications, products, and user experiences built on top of this foundation.
### The Extension Architecture
```
Your Custom Stablecoin (Extension)
↓ wraps/unwraps
M0 (Foundation)
↓ backed by
US Treasury Collateral
```
**Key Relationship:** Extensions are backed 1:1 by eligible collateral, meaning:
* 1 Extension Token = 1 unit of value (always redeemable)
* Extensions inherit the M0 platform's stability and regulatory clarity
* Extension contracts can earn yield (if approved by governance)
* Developers control how that yield flows to users, treasuries, or other destinations
### SwapFacility: The Conversion Engine
**SwapFacility** is the central hub enabling seamless 1:1 conversions between M0 Extensions. It acts as the exclusive gateway for wrapping/unwrapping operations, ensuring perfect value preservation across the entire M0 ecosystem.
All extensions must be governance-approved earners to participate, while permissioned extensions require additional authorized swapper roles. The contract implements advanced reentrancy protection and supports gasless transactions through permit functions. By concentrating all conversion logic in a single contract, SwapFacility creates **implicit liquidity** between all M0 Extensions without requiring separate trading pairs, making it the backbone of the M0 Extension ecosystem's interoperability.
### Why Build an Extension?
#### Full Customization
* **Yield Distribution**: Route yield to users, treasury, or split however you want
* **Access Controls**: Implement KYC, geographic restrictions, or institutional-only access
* **Fee Mechanisms**: Add transaction fees, management fees, or revenue sharing
* **Branding**: Create a fully branded stablecoin (YourAppUSD) or keep it generic
#### Inherited Benefits
* **Regulatory Clarity**: Built on M0's compliant, audited infrastructure
* **Shared Liquidity**: Tap into the M0 ecosystem's shared liquidity layer
* **Cross-Chain Ready**: Deploy on any chain where M0 operates
* **Battle-Tested Security**: Leverage M0's proven collateral and governance systems
#### Business Value
* **Control**: Unlike integrating someone else's stablecoin, you own the user relationship
* **Revenue**: Capture value through yield management and optional fees
* **Flexibility**: Evolve your stablecoin's features as your needs change
### Common Extension Types
#### Treasury Extensions
Route 100% of yield to a single treasury address while providing users with a stable, non-rebasing token experience.
**Use Cases**: Protocol treasuries, ecosystem development funds, corporate reserves
#### User Yield Extensions
Share yield with token holders while taking a small protocol fee for sustainability.
**Use Cases**: DeFi protocols, consumer apps, yield savings accounts
#### Institutional Extensions
Provide granular, per-account control with custom fee arrangements and whitelisting.
**Use Cases**: Prime brokerages, fintech platforms, institutional treasury management
### Who Should Build Extensions?
**Recommended for:**
* **Application Developers**: Games, payments, DeFi protocols wanting branded stablecoins
* **Ecosystem Builders**: L1/L2 chains needing native stablecoins for their ecosystems
* **Fintech Companies**: Businesses requiring custom compliance or yield distribution
* **Treasury Managers**: Organizations needing yield-bearing accounts with specific controls
**Consider Wrapped $M instead if:**
* You just need standard DeFi integration (AMM pools, lending markets)
* You prefer using an existing, established token rather than deploying your own
### Real-World Examples
* [**Wrapped $M**](/home/technical-specifications/wrapped-m-token/): The "reference implementation" showing standard DeFi compatibility
* [**Noble USD**](https://dollar.noble.xyz/): Cross-chain stablecoin serving the Cosmos ecosystem
### Technical Overview
Extensions are smart contracts that:
1. **Inherit from MExtension.sol**: A base contract providing core wrap/unwrap functionality
2. **Implement Custom Logic**: Add your unique features, access controls, and yield distribution
3. **Gain Earner Approval**: Get governance approval to earn yield
4. **Deploy Anywhere**: Launch on Ethereum, L2s, or any supported chain
**Key Functions Every Extension Must Implement:**
* `wrap(recipient, amount)`: Convert to your extension token
* `unwrap(recipient, amount)`: Convert your extension token back
* Custom yield claiming/distribution logic (your choice how this works)
### Getting Started
Ready to build your own stablecoin? The [Build section](/build/overview/) provides everything you need:
* **Model Selection Guide**: Choose the right template for your use case
* **Step-by-Step Deployment**: Deploy and configure your extension
* **Earner Approval Process**: Get governance approval for yield earning
* **Integration Examples**: Real code examples and best practices
## SwapFacility Deep Dive
### Architecture Overview
SwapFacility serves as the universal router for the M0 Extension ecosystem, enabling atomic conversions between any
approved M0 Extensions. The contract maintains minimal state while relying on the TTG governance system for extension
approval and access control.
#### Core Dependencies
* **M Token Base**: The foundational rebasing token that backs all extensions
* **TTG Registrar**: Governance-controlled registry that maintains the approved earners list
* **ReentrancyLock**: Advanced protection using transient storage for atomic operations
### Swap Operations
#### Extension-to-Extension Swaps
```solidity
function swap(address extensionIn, address extensionOut, uint256 amount, address recipient) external
```
**Process Flow:**
1. Validates both extensions are approved earners via `_revertIfNotApprovedExtension()`
2. Transfers extension tokens from user to SwapFacility
3. Calls `extensionIn.unwrap()` to convert to $M
4. Measures actual $M received (accounts for rounding differences in v1 extensions)
5. Approves and calls `extensionOut.wrap()` to mint target extension tokens
#### Direct $M Token Operations
```solidity
function swapInM(address extensionOut, uint256 amount, address recipient) external
function swapOutM(address extensionIn, uint256 amount, address recipient) external
```
**Access Control Differences:**
* `swapInM`: Anyone can convert $M to extensions
* `swapOutM`: Requires `M_SWAPPER_ROLE` or permission for specific extension
### Permission System
#### Two-Tier Extension Model
1. **Standard Extensions**: Open for general extension-to-extension swaps
2. **Permissioned Extensions**: Require special authorization for $M conversions
```solidity
function setPermissionedExtension(address extension, bool permissioned) external
function setPermissionedMSwapper(address extension, address swapper, bool allowed) external
```
#### Role-Based Access
* `DEFAULT_ADMIN_ROLE`: Contract administration and upgrades
* `M_SWAPPER_ROLE`: Global permission for `swapOutM` operations on standard extensions
### Security Features
#### Advanced Reentrancy Protection
The contract inherits from `ReentrancyLock` which implements transient storage-based locking:
```solidity
modifier isNotLocked() {
if (Locker.get() != address(0)) revert ContractLocked();
address caller_ = isTrustedRouter(msg.sender) ? IMsgSender(msg.sender).msgSender() : msg.sender;
Locker.set(caller_);
_;
Locker.set(address(0));
}
```
#### Trusted Router System
For complex integrations (like UniswapV3SwapAdapter), trusted routers can specify the original caller:
* Direct calls: `msg.sender` is stored as the locker
* Trusted router calls: `IMsgSender(msg.sender).msgSender()` retrieves the original user
Extensions can call `swapFacility.msgSender()` to get the true transaction initiator.
#### Governance Integration
All extension approvals are checked against the TTG Registrar:
```solidity
function _isApprovedEarner(address extension) private view returns (bool) {
return IRegistrarLike(registrar).get(EARNERS_LIST_IGNORED_KEY) != bytes32(0) ||
IRegistrarLike(registrar).listContains(EARNERS_LIST_NAME, extension);
}
```
### Gas Optimization
#### Permit Integration
All swap functions include permit variants supporting both EIP-2612 and EIP-712 signatures:
```solidity
function swapWithPermit(..., uint256 deadline, uint8 v, bytes32 r, bytes32 s) external
function swapWithPermit(..., uint256 deadline, bytes calldata signature) external
```
#### Balance Tracking Precision
For compatibility with Wrapped $M v1, the contract measures actual balance changes:
```solidity
uint256 mBalanceBefore = _mBalanceOf(address(this));
IMExtension(extensionIn).unwrap(address(this), amount);
amount = _mBalanceOf(address(this)) - mBalanceBefore;
```
### Technical Implementation
#### Immutable Architecture
```solidity
address public immutable mToken;
address public immutable registrar;
```
Core dependencies are immutable for security, while operational parameters remain upgradeable.
#### State Management
The contract maintains minimal state:
* `permissionedExtensions`: Extensions requiring special authorization
* `permissionedMSwappers`: Authorized swappers for specific permissioned extensions
#### Event System
```solidity
event Swapped(address indexed extensionIn, address indexed extensionOut, uint256 amount, address indexed recipient);
event SwappedInM(address indexed extensionOut, uint256 amount, address indexed recipient);
event SwappedOutM(address indexed extensionIn, uint256 amount, address indexed recipient);
```
### Economic Guarantees
SwapFacility maintains perfect 1:1 value relationships by design:
* No slippage or trading fees
* Atomic execution prevents arbitrage
* All conversions preserve dollar parity
* Extensions remain fully backed by eligible collateral
The contract creates implicit liquidity between all M0 Extensions without requiring separate AMM pools, enabling
seamless cross-extension value transfer.
import ZoomableImage from '@/components/ZoomableImage';
## Distribution Vault
Also called `TTGVault` or `Vault`.
### Overview
The Distribution Vault is a core component of M0's economic infrastructure designed to collect, account for, and
distribute various forms of protocol revenue and yield to Zero token holders. It serves as the primary mechanism for
value capture within the protocol's Two Token Governance (TTG) system, turning protocol operations into direct economic
benefits for governance participants.
### Purpose and Role in the Ecosystem
The Distribution Vault functions as:
1. **Revenue Collector**: Accumulates various forms of protocol revenue from multiple sources
2. **Yield Distributor**: Enables proportional distribution of accumulated tokens to Zero token holders
3. **Value Capture Mechanism**: Creates a direct incentive for governance participation
4. **Economic Bridge**: Links protocol activity with governance token value
### Architecture Overview
The Distribution Vault is referenced throughout the system as `TTGVault` or simply `vault`. Its address is established
during deployment and made accessible to other core contracts via the `Registrar` or specific deployer contracts.
### Revenue Sources
The Distribution Vault accumulates tokens from four primary sources:
#### 1. Excess Yield (from `MinterGateway`)
This represents a continuous source of revenue coming from the interest rate spread between minters and earners.
**Mechanism:**
* The `MinterGateway.updateIndex()` function calculates `excessOwedM()` - the difference between total owed $M (based on
minter interest rate) and actual total $M supply (growing at the earner interest rate)
* When positive, this excess amount is minted directly to the Distribution Vault
**Origin of Excess:**
* **Interest Rate Spread**: The inherent difference between the higher interest rate charged to minters vs. the lower
rate paid to earners (capped at 98% of the "safe rate")
* **Rounding Differences**: Accumulated from principal-to-present value conversions within the continuous indexing math
* **Penalties**: Charges imposed on minters for missed collateral updates or undercollateralization
**Code Implementation:**
```solidity
// In MinterGateway.sol
function updateIndex() public override(IContinuousIndexing, ContinuousIndexing) returns (uint128 index_) {
uint240 excessOwedM_ = excessOwedM();
if (excessOwedM_ > 0) IMToken(mToken).mint(ttgVault, excessOwedM_); // Mint $M to TTG Vault
index_ = super.updateIndex(); // Update minter index and rate
IMToken(mToken).updateIndex(); // Update earning index and rate
}
```
#### 2. Power token Auction Proceeds (from Power token)
The Power token contract auctions off newly inflated tokens in non-voting epochs, with proceeds directed to the vault.
**Mechanism:**
* Power token employs a Dutch auction mechanism where price decreases over time
* When users purchase Power token via the `buy()` function, the calculated cost in `cashToken` is sent to the
Distribution Vault
**Code Implementation:**
```solidity
// In Power token.sol
function buy(
uint256 minAmount_,
uint256 maxAmount_,
address destination_,
uint16 expiryEpoch_
) external returns (uint240 amount_, uint256 cost_) {
// ... calculations for amount_ and cost_ ...
emit Buy(msg.sender, amount_, cost_ = getCost(amount_));
_mint(destination_, amount_);
if (!ERC20Helper.transferFrom(cashToken(), msg.sender, vault, cost_)) revert TransferFromFailed();
}
```
#### 3. Forfeited Standard Governor Proposal Fees (from `StandardGovernor`)
Fees submitted with proposals in the `StandardGovernor` flow to the Distribution Vault if the proposal fails.
**Mechanism:**
* When creating a proposal via \`\`StandardGovernor`.propose()`, proposers pay a fee in the system's `cashToken`
* If the proposal succeeds, the fee is returned to the proposer
* If the proposal is defeated or expires, the fee is sent to the Distribution Vault via `sendProposalFeeToVault()`
**Code Implementation:**
```solidity
// In `StandardGovernor`.sol
function sendProposalFeeToVault(uint256 proposalId_) external {
ProposalState state_ = state(proposalId_);
if (state_ != ProposalState.Expired && state_ != ProposalState.Defeated) revert FeeNotDestinedForVault(state_);
uint256 proposalFee_ = _proposalFees[proposalId_].fee;
if (proposalFee_ == 0) revert NoFeeToSend();
address cashToken_ = _proposalFees[proposalId_].cashToken;
delete _proposalFees[proposalId_];
emit ProposalFeeSentToVault(proposalId_, cashToken_, proposalFee_);
_transfer(cashToken_, vault, proposalFee_);
}
```
#### 4. Direct/Arbitrary Transfers
The Distribution Vault can receive tokens via direct transfers from any account or contract. These tokens need to be
explicitly "registered" for distribution.
**Mechanism:**
* Tokens can be transferred directly to the vault contract address at any time using a standard
ERC20 `transfer` or `transferFrom`.
* To make these externally transferred tokens available for claiming, the external `distribute(token)` function must be
called for the specific `token_` address.
* The `distribute(token)` function calculates the increase in the vault's balance of `token_` since the last
time distribute was called for that token (using the internal `_lastTokenBalances` tracking). It then registers this
newly available amount (`amount_`) within the `currentEpoch_` (the epoch when `distribute()` is called).
* Crucially, Zero token holders who held tokens *during* this `currentEpoch_` become eligible to claim a share of this
distributed `amount_`. The claimable share for each eligible holder is calculated pro-rata based on their fractional
Zero token ownership as snapshotted at the *end* of that specific `currentEpoch_`.
**Code Implementation:**
```solidity
// In DistributionVault.sol
function distribute(address token_) external returns (uint256 amount_) {
uint256 currentEpoch_ = clock();
amount_ = getDistributable(token_);
emit Distribution(token_, currentEpoch_, amount_);
unchecked {
distributionOfAt[token_][currentEpoch_] += amount_; // Add to the distribution for the current epoch
_lastTokenBalances[token_] += amount_; // Track this contract's latest balance
}
}
function getDistributable(address token_) public view returns (uint256) {
return IERC20(token_).balanceOf(address(this)) - _lastTokenBalances[token_];
}
```
### Distribution Mechanism
The Distribution Vault implements a sophisticated epoch-based, pro-rata distribution system that ensures fair allocation
of accumulated tokens to Zero token holders.
#### Core Mechanics
1. **Epoch-Based Accounting**: Each token distribution is recorded in the current epoch (a time period, typically 15
days)
2. **Historical Balances**: Claims are calculated based on Zero token holders' balances during past epochs
3. **Pro-Rata Distribution**: Tokens are distributed proportionally to Zero token holdings
4. **Claimable Design**: Zero token holders must claim their share of distributed tokens
#### Distribution Process
1. **Accumulation**: Tokens flow into the vault through various mechanisms
2. **Registration**: The `distribute(token)` function is called, recording the new token amount for the current epoch
3. **Claiming**: Zero token holders call `claim()` or `claimBySig()` to receive their share for specific epochs
4. **Calculation**: The claimable amount is computed based on the holder's proportional ownership during each claimed
epoch
#### Key Functions
```solidity
// Allows claiming tokens for a specific address
function claim(
address token_,
uint256 startEpoch_,
uint256 endEpoch_,
address destination_
) external returns (uint256 claimed_)
// Allows claiming with a signature (helpful for gas-less transactions)
function claimBySig(
address account_,
address token_,
uint256 startEpoch_,
uint256 endEpoch_,
address destination_,
uint256 deadline_,
bytes memory signature_
) external returns (uint256 claimed_)
// Calculates the claimable amount for a given address, token, and epoch range
function getClaimable(
address token_,
address account_,
uint256 startEpoch_,
uint256 endEpoch_
) public view returns (uint256 claimable_)
```
### Technical Implementation Details
#### Contract Structure
The Distribution Vault inherits from StatefulERC712, which provides EIP-712 signature verification functionality:
```solidity
contract DistributionVault is IDistributionVault, StatefulERC712 {
// ... implementation ...
}
```
#### Key State Variables
```solidity
// The scale to apply when accumulating an account's claimable token to minimize precision loss
uint256 internal constant _GRANULARITY = 1e9;
// EIP-712 typehash for claim function
bytes32 public constant CLAIM_TYPEHASH = 0x4b4633c3c305de33d5d9cf70f2712f26961648cd68d020c2556a9e43be58051d;
// Address of the Zero Token contract
address public immutable zeroToken;
// Mapping of last recorded balance per token
mapping(address token => uint256 balance) internal _lastTokenBalances;
// Mapping of distributions per token, epoch, and amount
mapping(address token => mapping(uint256 epoch => uint256 amount)) public distributionOfAt;
// Mapping of claimed status per token, epoch, and account
mapping(address token => mapping(uint256 epoch => mapping(address account => bool claimed))) public hasClaimed;
```
#### Signature-Based Claims
The Distribution Vault supports signature-based claims, allowing users to authorize others to claim on their behalf:
```solidity
function claimBySig(
address account_,
address token_,
uint256 startEpoch_,
uint256 endEpoch_,
address destination_,
uint256 deadline_,
uint8 v_,
bytes32 r_,
bytes32 s_
) external returns (uint256) {
// ... signature verification logic ...
// ... deadline verification ...
return _claim(account_, token_, startEpoch_, endEpoch_, destination_);
}
```
### Economic Implications
The Distribution Vault creates several important economic effects:
1. **Zero token Value Accrual**: By directing protocol revenue to Zero token holders, it increases the token's economic
value
2. **Governance Incentive Alignment**: Creates a direct financial incentive for governance participation
3. **Sustainable Protocol Economics**: Ensures that excess yield and protocol fees flow back to governance participants
4. **Diversified Revenue Streams**: Collects different token types from multiple sources, creating a robust revenue
model
### Integration with the Wrapped $M Token (Extended)
The Distribution Vault also interacts with the Wrapped $M (wM) system:
1. **Excess Yield Destination**: The wM contract can send excess yield to the Distribution Vault via its `claimExcess()`
function
2. **Economic Loop Completion**: This creates a complete economic loop where even wrapped token operations can generate
value for governance participants
### Conclusion
The Distribution Vault stands as a critical infrastructure component in the M0 protocol, serving as the central hub for
value capture and redistribution. Through its sophisticated pro-rata distribution mechanism, it ensures that protocol
operations consistently generate value for governance participants, creating strong economic incentives for system
participation and alignment.
By collecting revenue from multiple sources and making it claimable by Zero token holders, the vault creates a direct
financial link between protocol usage and governance value, strengthening the economic sustainability of the entire
ecosystem.
One can check straight on the [dashboard](http://dashboard.m0.org) the number of $M accumulated by the vault and not
claimed so far.
import ZoomableImage from "@/components/ZoomableImage";
## Protocol Overview
Welcome to the core of the M0 documentation. This section is a comprehensive, first-principles exploration of the M0 Protocol.
Here, you will find the foundational documents, detailed technical architecture, and smart contract specifications that define how the M0 ecosystem functions onchain and offchain. This is the definitive reference for developers, auditors, and partners who require a deep and thorough understanding of the system.
High-level architecture showing how M0 Governance configures the protocol's core components.
### Explore the M0 Protocol
This section is organized into three main pillars, moving from the conceptual to the highly technical.
export function CardGrid() {
const cards = [
{
title: 'Whitepaper',
description: 'The core M0 protocol is a coordination layer enabling permissioned actors to generate M0-powered stablecoins with a fungible building block.',
href: '/home/fundamentals/whitepaper/',
iconClass: 'homepageCardImage-notebook', // Using an existing icon class
},
{
title: 'Adopted Guidance',
description: 'The Adopted Guidance outlines the core rules of engagement that exist outside of the enforceable domain of the protocol smart contracts for the various actors in the M0 ecosystem.',
href: '/home/fundamentals/adopted-guidance/description/',
iconClass: 'homepageCardImage-notebook', // Using an existing icon class
},
{
title: 'Technical Documentation',
description: 'Dive into the "how". Explore detailed explanations of each core smart contract and system, from token mechanics and governance to cross-chain architecture.',
href: '/home/technical-documentations/m-token/overview/',
iconClass: 'homepageCardImage-flow', // Using an existing icon class
},
{
title: 'Technical Specifications',
description: 'For deep integration. Access low-level smart contract references, function definitions, events, and errors for the core M0 assets.',
href: '/home/technical-specifications/m-token/',
iconClass: 'homepageCardImage-playlist', // Using an existing icon class
},
]
return (
)
}
## Whitepaper
The core M0 protocol is a coordination layer enabling permissioned actors to generate $M, a fungible stablecoin building block.
### Table of Contents
***
##### [Abstract](/home/fundamentals/whitepaper/abstract/)
##### [I. Introduction](/home/fundamentals/whitepaper/introduction/)
##### [II. Protocol](/home/fundamentals/whitepaper/protocol/)
##### [III. Governance](/home/fundamentals/whitepaper/governance/)
##### [IV. The M0 Economy](/home/fundamentals/whitepaper/economy/)
##### [V. Offchain Ecosystem](/home/fundamentals/whitepaper/offchain-ecosystem/)
***
##### [Glossary](/get-started/resources/glossary/)
##### [Whitepaper Disclosures](/get-started/resources/disclosures/)
***
##### [Download PDF Version](https://github.com/m0-foundation/documentation/tree/main/whitepaper)
import ZoomableImage from '@/components/ZoomableImage';
## II. Protocol
The M0 protocol is a set of immutable smart contracts implemented for the Ethereum Virtual Machine.
The M0 protocol receives all external inputs from a governance mechanism called the M0 Two Token Governor,
[TTG](/get-started/resources/glossary/#ttg), (see [Governance](/home/fundamentals/whitepaper/governance/)). The M0
protocol is a coordination tool for actors permissioned by governance, namely
[Minters](/get-started/resources/glossary/#minter), [validators](/get-started/resources/glossary/#validator), and
[Earners](/get-started/resources/glossary/#earner).
### II.I Operation
The M0 Protocol is permissioned through the Two Token Governor ([TTG](/get-started/resources/glossary/#ttg)) mechanism.
The primary actors in the protocol are called [Minters](/get-started/resources/glossary/#minter) and
[validators](/get-started/resources/glossary/#validator) (see
[Governance Controlled Protocol Actors](/home/fundamentals/whitepaper/protocol/#iiii-governance-controlled-protocol-actors)).
Once permissioned by the [TTG](/get-started/resources/glossary/#ttg), [Minters](/get-started/resources/glossary/#minter)
and [validators](/get-started/resources/glossary/#validator) are able to access certain methods in the smart contracts
which facilitate the creation, maintenance, and destruction of $M. $M is a standard ERC20 token. The core operating
condition of the M0 protocol is to ensure that all $M in existence is backed by an equal or greater amount of
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) value that is held in an
[Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) (see
[offchain Actors and Components](/home/fundamentals/whitepaper/offchain-ecosystem/#offchain-actors-and-components)).
\:::note In this context, the protocol acts as the enforcer of a set of rules which controls the generation of M, the
validation of collateral custody and value, and the assessment of fees when appropriate. :::
#### II.I.I Generation of M
In order to generate $M, [Minters](/get-started/resources/glossary/#minter) must have a sufficient offchain balance of
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) which is represented onchain by a frequently
updated and validated number, known as the onchain Collateral Value.
[Minters](/get-started/resources/glossary/#minter) call the Update Collateral method to put this number onchain. They
must pass the amount, the list of signing [validators](/get-started/resources/glossary/#validator), a list of timestamps
associated with the Validator signatures, and valid signature data (from
[validators](/get-started/resources/glossary/#validator)). Whenever timestamps and signature data is passed to a method,
the contracts will take the minimum timestamp and the minimum threshold of signatures as defined by the
[TTG](/get-started/resources/glossary/#ttg) (see
[Governance Controlled TTG Parameters](/home/fundamentals/whitepaper/governance/#governance-controlled-ttg-parameters-1)).
Optionally, they can pass a hash of arbitrary metadata and any open Retrieval IDs (see
[Retrieving Free Collateral](/home/fundamentals/whitepaper/protocol/#retrieving-free-collateral)) into the method as an
argument. The Metadata Hash can be used to retrieve the actual offchain metadata, which can serve to add context to the
update, while Retrieval IDs allow [Minters](/get-started/resources/glossary/#minter) to remove outstanding balance
subtractions (see [Retrieving Free Collateral](/home/fundamentals/whitepaper/protocol/#retrieving-free-collateral)).
Signature validation may either use standard ecrecover, which allows for the Minter to obtain the signature offchain, or
[EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) onchain contract signatures. The collateral balance is an
attestation to the value of the [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) held in an
[Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) (see
[offchain Actors and Components](/home/fundamentals/whitepaper/offchain-ecosystem/#offchain-actors-and-components) for
further details).
In order to post the value of their [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) onchain,
the [Minters](/get-started/resources/glossary/#minter) will need to provide the signature data (from
[validators](/get-started/resources/glossary/#validator)) in the transaction. The presence of the Validator’s signature
is to reinforce that the value of the [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) is
correct and reflects the most up-to-date snapshot of the offchain balances.
[Minters](/get-started/resources/glossary/#minter) must update their
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) number onchain and with a valid Validator
signature at least once every [Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval)
(see
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters)).
If a Minter fails to call Update Collateral within
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) of the previous time they
called it, their onchain Collateral Value is assumed to be 0. If the Minter cannot provide valid signature data (from
[validators](/get-started/resources/glossary/#validator)), they cannot successfully call the method. Each time this
method is called it will accrue the [Minter Rate](/get-started/resources/glossary/#minter-rate) (see
[Protocol Fees](/home/fundamentals/whitepaper/protocol/#protocol-fees)) on the Minter’s current balance of Owed $M. If
any rules are being violated at the time of the method being called, it will also charge
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) on the Minter’s balance which is in violation (see
[Protocol Fees](/home/fundamentals/whitepaper/protocol/#protocol-fees)).
\:::success **Example** [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) for $M has been
deemed to be 0-90 day T-bills. Minter 1 has $10,000,000 of T-bills sitting in an
[Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution). Minter 1 calls Update
Collateral and passes 10,000,000, and a valid Validator signature as arguments. The onchain Collateral Value of the
Minter is now 10,000,000. The next day, $1,000,000 of the T-bills mature and convert to bank deposits, which are not
considered [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral). The Minter calls Update
Collateral and passes 9,000,000, and a valid Validator signature as arguments. The onchain collateral balance of the
Minter is updated to 9,000,000. :::
Once a Minter has updated their onchain collateral they are able to generate $M. They do so by calling the Propose Mint
method and passing in the amount of $M they’d like to generate and the address which they would like to generate the $M
to. Once this method is called, it will first call Get Present Amount on the Minter's current balance of Owed $M. It
will then check to ensure that the onchain Collateral Value multiplied by the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio) (see
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters))
is greater than the amount of total Owed $M from the Minter, including the amount they are currently attempting to
generate and/or Retrieve (see
[Retrieving Free Collateral](/home/fundamentals/whitepaper/protocol/#retrieving-free-collateral)). If these checks are
passed the method will output a Mint ID which corresponds to the Propose Mint. A Minter can only have one outstanding
Mint ID at any given time.
If after the [Mint Delay](/get-started/resources/glossary/#mint-delay) (see
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters))
the Mint ID has not been canceled by the Validator (see
[Cancel and Freeze](/home/fundamentals/whitepaper/protocol/#cancel-and-freeze)), the Minter may call the Mint method and
pass the Mint ID as an argument to execute the Propose Mint. The
[Mint Delay](/get-started/resources/glossary/#mint-delay) was introduced to avoid atomic Update Collateral calls and
Mint calls, and to provide the network of [validators](/get-started/resources/glossary/#validator) with sufficient
opportunity to intervene in the minting process if something is seemingly wrong (see
[Cancel and Freeze](/home/fundamentals/whitepaper/protocol/#cancel-and-freeze)). The Minter must call Mint before the
[Propose Mint Time To Live](/get-started/resources/glossary/#propose-mint-time-to-live) has expired (see
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters)).
[Minters](/get-started/resources/glossary/#minter) can destroy Owed $M at any time by calling the Burn method and
passing in their Minter Address and the amount of $M they’d like to burn as arguments. Any address can repay $M owed by
a Minter by calling the Burn method and passing in the amount and Minter address as arguments.
#### II.I.II Protocol Fees
There are two fees assessed on [Minters](/get-started/resources/glossary/#minter) in the M0 protocol.
The first is called [Minter Rate](/get-started/resources/glossary/#minter-rate), a governance controlled parameter,
which is levied continuously on the Minter’s balance of Owed $M. This fee compounds on a continuous basis. The
beneficiaries of [Minter Rate](/get-started/resources/glossary/#minter-rate) are the Earn Mechanism (see
[The Earn Mechanism](/home/fundamentals/whitepaper/protocol/#the-earn-mechanism)) and the
[`ZERO`](/get-started/resources/glossary/#zero) holders (see [Governance](/home/fundamentals/whitepaper/governance/)).
The second is [Penalty Rate](/get-started/resources/glossary/#penalty-rate), another governance controlled parameter,
which is levied on balances that are in violation of protocol rules and has the same beneficiaries as the
[Minter Rate](/get-started/resources/glossary/#minter-rate).
\:::note One of the primary invariants of the protocol is that the balance of a Minter’s Owed $M should not exceed the
onchain Collateral Value (sans open Retrieval IDs) multiplied by the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio). :::
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) is imposed upon any balance in excess of this amount. If a
Minter has not called Update Collateral within
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval), they will incur
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) on their entire balance of Owed $M for each
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) that they miss – i.e. when
Update Collateral is not called in the [TTG](/get-started/resources/glossary/#ttg)-specified time the system interprets
its value to be [`ZERO`](/get-started/resources/glossary/#zero). Unlike
[Minter Rate](/get-started/resources/glossary/#minter-rate),
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) is not continuously levied on the Minter’s balance of Owed
M, but is charged discretely as a one-time percentage fee on their delinquent balance at the moment the balance is
checked, and then added to the Minter’s Owed $M. Collecting [Minter Rate](/get-started/resources/glossary/#minter-rate)
is contained in the Get Present Amount method, which is exclusively embedded in all other Minter methods, including
Burn, and cannot be called independently. The [Minter Rate](/get-started/resources/glossary/#minter-rate) is
mechanically affected by updating a global index value which is applied to all Owed $M in the Minter’s balance, as is
[Penalty Rate](/get-started/resources/glossary/#penalty-rate).
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) is contained in the Impose Penalty method, which is
exclusively embedded in the Update Collateral, Burn and Deactivate Minter methods. When Impose Penalty is called in
conjunction with Update Collateral, it checks for both missed
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) periods and the Minter’s
balance of Owed $M relative to their onchain Collateral Value discounted by the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio). When it is called in conjunction with Burn, it only checks
for missed [Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) periods. This is
done to ensure that the Minter is not penalized on the same errant balance more than once. Impose Penalty will also
account for whether it has already been called in the current
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) period and will not charge a
Minter twice for the same missed period.
\:::success **Example** A Minter calls Update Collateral; the Get Present Amount method is called along with the Impose
Penalty method. The Minter’s balance of Owed $M prior to the method call is 8,000,000 $M. First, Get Present Amount is
used to fetch the latest index and apply the [Minter Rate](/get-started/resources/glossary/#minter-rate) to the Minter’s
balance of Owed M, accounting for continuous compounding. Assume that this increases the Minter’s Owed $M to 8,000,010
M. If the Minter’s onchain Collateral Value is 8,000,000 and the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio) is 90%, then the maximum amount of Owed $M Minter 1 should
have is 7,200,000 M– but the actual onchain number is 8,000,010 $M. Therefore Minter 1 will incur
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) on (8,000,010 $M - 7,200,000 M) = 800,010 $M. If
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) is 0.01%, then the Minter’s Owed $M is incremented to
8,000,010 $M + 800,010 M\* 1.0001 = 8,800,100.001 $M. :::
The following is a diagram which demonstrates a hypothetical sequence where a Minter incurs
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) charges. The example below describes this hypothetical
sequence.
\:::success Example (some balances are rounded) Assume that the Mint to Collateral Ratio is 90%,
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) is 24 hours,
[Minter Rate](/get-started/resources/glossary/#minter-rate) is 5% APY (and therefore \~0.00058% per hour), and Penalty
[Minter Rate](/get-started/resources/glossary/#minter-rate) is 0.02%.
* On Day 1, a Minter calls Update Collateral and passes in 100 as the value. Its Owed $M is 0. Later that day (Day 1.1)
the Minter generates 90 $M.
* On Day 2 the Minter fails to call Update Collateral.
* On Day 3 the Minter calls Burn and passes in 50.043 $M. Assume that less than 48 hours have passed since the Mint call
on Day 1. First, Get Present Amount is called and applies the latest index to the Minter’s balance of 90 $M. This
increases the Minter’s Owed $M to (90 \_ (1 + (0.0000058 \_ 48))) = 90.025 $M. Next, Impose Penalty is called and is
applied to the Minter’s updated balance. Since the Minter missed one
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) period (on Day 2), they are
penalized one time. The Minter’s Owed $M is increased to (90.025 \_ (1 + (0.0002 \_ 1)) = 90.043 $M. The amount passed
into the Burn method (50.043) is now subtracted from this new balance and reduces the Minter’s Owed $M to (90.043 -
50.043) = 40 $M. Recall that burn only checks for missed
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) periods and does not check
the Minter’s current onchain Collateral Value and therefore does not penalize any currently errant balance. The Minter
then calls Burn again on Day 3 and passes in 30.0014 $M. Assume 6 hours have passed since the previous Burn call.
First, Get Present Amount is called and applies the latest index to the Minter’s balance of 40 $M. This increases the
Minter’s Owed $M to (40 \_ (1 + (0.0000058 \_ 6))) = 40.0014 $M. Impose Penalty is then run but does not charge the
Minter an additional [Penalty Rate](/get-started/resources/glossary/#penalty-rate) because it has already paid for all
missed [Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) periods. The amount
passed into the Burn method (30.0014) is now subtracted from this new balance and reduces the Minter’s Owed $M to
(40.0014 - 30.0014) = 10 $M. Finally, also on Day 3, the Minter calls Update Collateral and passes in 0 (most likely
because their [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) has matured and is sitting
in ineligible bank deposits). Assume another 6 hours have passed. Once again, Get Present Amount is run to apply the
latest index to the Minter’s balance of $M. This increases the Minter’s Owed $M by (10 \_ (1 + (0.0000058 \_ 6))) =
10.00035 $M. Next Impose Penalty is called. There are no charges for missed periods because these were already paid
when the Minter called Burn. A check for an errant balance is now run and produces 10.00035 $M since the Minter’s
entire balance is in excess of their maximum permitted Owed $M due to the onchain Collateral Value being 0. The
Minter’s Owed $M is increased to (10.00035 \* 1.0002) = 10.00235 $M.
\:::
#### II.I.III Cancel and Freeze
An overview of the two methods which can be used to stop an errant generation of $M or to stop an errant Minter in the
case of an emergency.
The first method, Cancel, can be called by any Validator on any Mint ID associated with the generation of $M. The
calling actor must pass the Mint ID as an argument to the method. Calling this method will cancel the specified Mint ID
and cancel the proposal. The Cancel method can be called at any time until Mint is called.
[Minters](/get-started/resources/glossary/#minter) do not have access to the Cancel method because submitting a new
Propose Mint will automatically cancel and replace any that are currently outstanding. The second method, Freeze, can be
called by any Validator on any Minter by passing the Minter address as the argument of the method. Calling Freeze will
disable Propose Mint and Mint for [Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time) (see
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters)).
It can be called multiple times on the same Minter to reset the
[Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time) window. If Freeze is called during an already
existing [Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time), the
[Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time) window will restart from the beginning.
\:::success **Example** A Validator calls Freeze on a Minter and the
[Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time) window is 6 hours. After 5 hours, the
Validator calls Freeze again on the same Minter. The Minter is now frozen for an additional 6 hours. This means that the
Minter will be frozen for 11 hours in total, unless a Validator calls Freeze again before the end of the second 6 hour
period. :::
These methods can be thought of as being a part of a series of escalations the system (first through
[validators](/get-started/resources/glossary/#validator) and then through the
[TTG](/get-started/resources/glossary/#ttg)) can levy against an errant Minter. The final escalation being removal of
the Minter. Removal of a Minter would similarly be done via a [TTG](/get-started/resources/glossary/#ttg) proposal.
#### II.I.IV Retrieving Free Collateral
This is an extensive description of the process of retrieving collateral.
Any Minter with an excess of offchain value (both
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) and other forms of value that may reside in
the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution)) relative to their Owed $M,
can remove this value from the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
They can do so by calling the Propose Retrieval method and passing the amount they wish to retrieve from custody.
Like most methods this will first call Get Present Amount. After that it will check that the onchain Collateral Value,
after subtracting the amount the Minter is trying to retrieve and any other open Retrieval IDs, is sufficient to support
the Minter’s remaining balance of Owed $M. Unlike Propose Mint, which is limited to one Mint ID at a time, there is not
a limit to the number of outstanding Retrieval IDs. If this check passes, it will sideline this balance to be deducted
from future calculations where it is relevant. Finally it outputs a Retrieval ID. The subtraction from the onchain
Collateral Value, when relevant, will remain until the Retrieval ID is closed.
In order to close the Retrieval ID and eliminate the subtraction on the Minter’s onchain Collateral Value, the Minter
must pass the Retrieval ID into an Update Collateral. In signing off on this transaction, the Validator is attesting
that the Retrieval ID has been fully processed offchain, or will not be processed at all, and that the new onchain
Collateral Value is correct.
#### II.I.V The Earn Mechanism
This is an extensive description of the Earn mechanism within the M0 protocol.
The Earn Mechanism is a mechanism in the protocol which allows Approved
[Earners](/get-started/resources/glossary/#earner) (see
[Governance Controlled Protocol Actors](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-actors))
to earn the [Earner Rate](/get-started/resources/glossary/#earner-rate) (see
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters)).
The [Earner Rate](/get-started/resources/glossary/#earner-rate), while input as an explicit value from the
[TTG](/get-started/resources/glossary/#ttg), is bound in the smart contracts to be the lower of its input value or the
maximum it can be without expending more $M than is accruing from
[Minter Rate](/get-started/resources/glossary/#minter-rate). To elaborate, the utilization of the Earn Mechanism can be
considered to be the total amount of Owed $M that is currently paying the
[Minter Rate](/get-started/resources/glossary/#minter-rate) (hereon referred to as active M) divided by the total amount
of $M in the Earn Mechanism. If a Minter is depermissioned by the [TTG](/get-started/resources/glossary/#ttg), their $M
will be deducted from the active $M and thus lower utilization. The
[Earner Rate](/get-started/resources/glossary/#earner-rate) is then the minimum of the value input by the
[TTG](/get-started/resources/glossary/#ttg), or the [Minter Rate](/get-started/resources/glossary/#minter-rate)
multiplied by utilization, which represents the lowest rate that would be safe to offer before more $M is being paid to
[Earners](/get-started/resources/glossary/#earner) than is being collected from
[Minters](/get-started/resources/glossary/#minter).
Once approved by the [TTG](/get-started/resources/glossary/#ttg), an Approved Earner can call the Start Earning method.
This will check if the address is on the Approved [Earners](/get-started/resources/glossary/#earner) list and if so the
address will begin to earn the [Earner Rate](/get-started/resources/glossary/#earner-rate) on a continuously compounding
basis. If an address is removed from the Earner’s list, Stop Earning can be called with the address in question passed
as an argument to the method, which will cease the accrual of the
[Earner Rate](/get-started/resources/glossary/#earner-rate) on the address’ balance.
#### II.I.VI Removing a Permissioned Actor
Overview of the process for removing permissioned actors in the M0 Protocol.
Permissioned actors can be removed from the system by passing a proposal in the
[TTG](/get-started/resources/glossary/#ttg) mechanism removing them from a specific list (e.g. the Minter list). Once an
actor is removed they are no longer able to call the methods in the contracts, preventing them from engaging in further
activity with the protocol.
Once a Minter is removed from the Minter list, they cease to pay the
[Minter Rate](/get-started/resources/glossary/#minter-rate) on their Owed $M. The value of current Owed $M, and
potentially penalties for missed intervals, is stored for repayment by the Minter or anyone interested in their offchain
collateral retrieval. This is to ensure that $M does not get paid to the Earn Mechanism and to the
[`ZERO`](/get-started/resources/glossary/#zero) holders if a Minter is no longer actively in the system.
The Burn method always remains available to anyone even if a Minter is no longer permissioned. This ensures that
offchain actors can facilitate the wind-down of the Minter’s operations and destroy the Owed $M in order to retrieve the
value from the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
Once a Minter is removed from the Minter list, any actor in the system can call the Deactivate Minter method to cease
the accrual of [Minter Rate](/get-started/resources/glossary/#minter-rate) and the imposition of further
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) charges.
#### II.I.VII Example Interactions and Flows
The following is an example of how Permissioned Actors are intended to interact with the protocol, and how $M is
intended to flow through the protocol.
* **Step 1**: [Minters](/get-started/resources/glossary/#minter),
[validators](/get-started/resources/glossary/#validator), and [Earners](/get-started/resources/glossary/#earner)
propose their addresses to the M0 [TTG](/get-started/resources/glossary/#ttg).
* **Step 2**: The [TTG](/get-started/resources/glossary/#ttg) accepts or rejects the proposals.
* **Step 3**: [Minters](/get-started/resources/glossary/#minter) that were approved by the
[TTG](/get-started/resources/glossary/#ttg) are now on the Minter List. These
[Minters](/get-started/resources/glossary/#minter) call for their first onchain Collateral Value update.
* **Step 4**: This Minter has contracted with at least one Validator offchain. The Validator(s) have full access to the
records and statements of the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
The Validator(s) will check to ensure that the proposed onchain Collateral Value is less than the dollar value of the
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) in the
[Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution). Once they have confirmed this
to be true, they will provide the Minter with their signature and a timestamp from when they performed the balance
check.
* **Step 5**: Once the Minter has obtained a valid signature and timestamp from the Validator(s) they will call Update
Collateral to push the balance onchain.
* **Step 6**: Now that the Minter has a positive onchain Collateral Value, they are able to generate $M. They will call
the Propose Mint method and specify the amount they wish to generate. As long as this amount is within the bounds of
the current onchain Collateral Value (excluding any open Retrieval IDs) multiplied by the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio), the method will output a Mint ID. This Mint ID will be
inactionable for some [Mint Delay](/get-started/resources/glossary/#mint-delay), and then actionable for
[Propose Mint Time To Live](/get-started/resources/glossary/#propose-mint-time-to-live).
* **Step 7**: The Mint ID must be outstanding for [Mint Delay](/get-started/resources/glossary/#mint-delay). This is to
ensure the superset of [validators](/get-started/resources/glossary/#validator) have the opportunity to scrutinize the
Propose Mint and call the Cancel and/or Freeze methods if something is amiss. Once
[Mint Delay](/get-started/resources/glossary/#mint-delay) has passed, the Minter can call Mint and generate the $M.
* **Step 8**: Now that the $M is generated, the Minter begins to pay
[Minter Rate](/get-started/resources/glossary/#minter-rate) on their Owed $M. They will be subject to
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) charges on this balance if they do not keep their
onchain Collateral Value up to date or allow their onchain Collateral Value to decrease below the permitted level. If
the latter circumstance occurs, it is likely because the assets comprising the offchain collateral have matured and
are sitting in bank deposits.
* **Step 9**: Assume that this Minter now wishes to repay some of the $M they have generated and to retrieve a portion
of the collateral from the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
The Minter will first call Burn and specify the amount of $M they’d like to repay. This will reduce their Owed $M by
this amount. Assuming that now there is a positive spread between the permitted amount of $M that the Minter can
generate and their Owed M, they can call the Retrieve method. The Retrieve method will first check if their onchain
Collateral Value, after the retrieval, is in compliance with the protocol’s rules. If it is, the method will output a
Retrieval ID and reduce the Minter’s onchain Collateral Value by the amount specified.
* **Step 10**: The Minter can now go to the operator of the
[Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) and show them the Retrieval ID
and request that they allow them to redeem the corresponding value.
* **Step 11**: Once the transaction has completed and cleared the Minter will call Update Collateral and input the new
Collateral Value and the Retrieval ID in order to remove the subtraction to onchain Collateral Value associated with
the Retrieval ID. The Minter once again requires the Validator signature data and timestamp for the transaction to
succeed.
### II.II Governance Controlled Protocol Actors
List of Governance controlled protocol actors.
##### [Minters](/get-started/resources/glossary/#minter)
A list of addresses (the Minter list, where the addresses are known as Minter address) maintained by the
[TTG](/get-started/resources/glossary/#ttg) mechanism which are able to access the minting functionality. The minting
functionality allows for addresses on the Minter list to update onchain Collateral Value associated with their Minter
address, Propose Mint, Mint, Burn, and to Retrieve offchain collateral.
##### [Validators](/get-started/resources/glossary/#validator)
A list of addresses (the Validator list, where the addresses are known as Validator address) maintained by the
[TTG](/get-started/resources/glossary/#ttg) mechanism which act as a security layer for protocol.
[validators](/get-started/resources/glossary/#validator) are required to provide signatures for the Update Collateral
method. They also have the ability to call the Cancel method on any Mint ID, and the Freeze method on any Minter
address.
##### [Earners](/get-started/resources/glossary/#earner)
A list of addresses (the Earner list, where the addresses are known as Earner address) maintained by the
[TTG](/get-started/resources/glossary/#ttg) mechanism. These addresses are able to control whether they earn the
[Earner Rate](/get-started/resources/glossary/#earner-rate).
### II.III Governance Controlled Protocol Parameters
\:::note These values will be set after launch through the [TTG](/get-started/resources/glossary/#ttg) mechanism. It is
not possible to deploy the protocol with preset parameters. :::
##### [Minter Rate](/get-started/resources/glossary/#minter-rate)
The annualized percentage charged continuously to [Minters](/get-started/resources/glossary/#minter) on their Owed $M.
It is alterable with a Standard Proposal.
Logic: This annualized percentage should (generally) be less than the average rate on the
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) being earned by
[Minters](/get-started/resources/glossary/#minter). This spread, adjusted for the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio), is the profit margin of the Minter.
##### [Penalty Rate](/get-started/resources/glossary/#penalty-rate)
The percentage charged on Owed $M that is in excess of the amount a Minter is permitted to have generated. It is
assessed any time Impose Penalty is called, which is embedded in both Update Collateral and Burn. It is alterable with a
Standard Proposal. This is a fixed percentage and not an annualized rate. Logic: This percentage should be sufficiently
high to deter Minter offenses, but not so high as to overly punish [Minters](/get-started/resources/glossary/#minter)
that are victims of circumstance.
\:::success **Example** Minter 1 has 1,000,000 Owed $M but has not updated their onchain Collateral Value within
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval), and hence the onchain
Collateral Value is 0. Whenever they call Update Collateral or Burn, and Impose Penalty is consequently called, they
will pay [Penalty Rate](/get-started/resources/glossary/#penalty-rate) on 1,000,000 - (0 \_
[Mint Ratio](/get-started/resources/glossary/#mint-ratio)). So they will pay
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) on their full debt. 1,000,000 \_ .01 = 10,000 $M in
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) charges. This assumes that only one
[Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval) period was missed. :::
##### [Earner Rate](/get-started/resources/glossary/#earner-rate)
The annualized percentage paid to $M in the Earn Mechanism. If the cumulative $M paid out via the Earn Mechanism is
going to be greater than the amount of $M being generated by the
[Minter Rate](/get-started/resources/glossary/#minter-rate), the
[Earner Rate](/get-started/resources/glossary/#earner-rate) is automatically discounted to whichever percentage will
reduce this mismatch to 0. [`ZERO`](/get-started/resources/glossary/#zero) holders receive all remaining $M that is not
paid out to the Earn Mechanism for their participation in protocol governance. It is alterable with a Standard Proposal.
Logic: This annualized percentage should be consistent with the yield demanded by institutional holders of $M. It is
mechanically prevented from exceeding the cumulative level of $M generated by the
[Minter Rate](/get-started/resources/glossary/#minter-rate). It should not be set so low that it results in insufficient
demand for $M and thus an inefficient market for [Minters](/get-started/resources/glossary/#minter).
##### [Mint Ratio](/get-started/resources/glossary/#mint-ratio)
This percentage is the fraction of a Minter’s onchain Collateral Value that they can generate in $M. It effectively
controls the leverage of a Minter and the over-collateralization of $M. It is alterable with a Standard Proposal. Logic:
This percentage controls the leverage of [Minters](/get-started/resources/glossary/#minter) and the
over-collateralization of $M. It should be set high enough to encourage attractive Minter economics, but not so high
that it compromises the stability of $M.
##### [Mint Delay](/get-started/resources/glossary/#mint-delay)
This amount of time is the period between when a Minter has called Propose Mint and when they can first call Mint. It
serves as a protective measure to ensure all actors have sufficient time to audit each Mint. It is alterable with a
Standard Proposal. Logic: This amount of time should be long enough to ensure proper auditability and to afford
[validators](/get-started/resources/glossary/#validator) a chance to call Cancel or Freeze if necessary. It should not
be so long that it introduces unnecessary friction into the Minting process and reduces the efficiency of
[Minters](/get-started/resources/glossary/#minter) and the stability of $M.
##### [Propose Mint Time To Live](/get-started/resources/glossary/#propose-mint-time-to-live)
This is the amount of time after the [Mint Delay](/get-started/resources/glossary/#mint-delay) that a Proposed Mint has
to be called before it expires. It serves as a protective measure to ensure that
[Minters](/get-started/resources/glossary/#minter) cannot call Propose Mint and then execute the Mint at a much later
date. It is alterable with a Standard Proposal. Logic: This amount of time should be long enough to give
[Minters](/get-started/resources/glossary/#minter) a chance to react to the
[Mint Delay](/get-started/resources/glossary/#mint-delay) lapsing and execute their Mint, without being so long that it
compromises the integrity of the previous Validator checks.
##### [Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval)
This amount of time is the period between which Update Collateral must be called by a Minter. If they do not call Update
Collateral within this amount of time after their previous call, their onchain Collateral Value is assumed to be 0 and
they will incur [Penalty Rate](/get-started/resources/glossary/#penalty-rate) on the next update. It is alterable with a
Standard Proposal. Logic: This amount of time should be long enough to ensure that
[validators](/get-started/resources/glossary/#validator) can reliably check the status of the offchain structures and
balances, but not so long that it compromises the integrity of those checks.
##### Update Collateral Threshold
This number of signatures is the minimum number of Validator signatures required to execute Update Collateral. If a
Minter cannot provide this number of signatures, they cannot successfully call Update Collateral. It is alterable with a
Standard Proposal. Logic: This number of signatures should ensure that the Update Collateral process is as secure as
possible given the number of [validators](/get-started/resources/glossary/#validator) in the network. It should not be
set so high that [Minters](/get-started/resources/glossary/#minter) cannot reliably call Update Collateral.
##### [Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time)
This amount of time is the duration for which a Minter will not be able to call Propose Mint or Mint after having the
Freeze method called by a Validator on their address. It is alterable with a Standard Proposal. Logic: This amount of
time should be sufficient for the Minter to remedy an issue, but not so long that it materially disrupts its normal
course of business.
import ZoomableImage from "@/components/ZoomableImage";
## V. Offchain Ecosystem
The M0 protocol relies on the presence of several offchain actors and components, and a feedback process referred to as “guidance” to function properly.
### V.I Guidance
##### The Guidance feedback loop.
It is anticipated that as the M0 ecosystem grows, an increasing number of stakeholders will have an interest in its continued development and improvement. These stakeholders are referred to as Think Tanks. Much as the Basel Committee and its cooperating international regulators have provided (theoretically non-committal) guidance and rules for the banking sector, it is anticipated that similar groups and perhaps new M0-specific institutions will provide guidance for the M0 ecosystem and protocol. The protocol is designed in such a way that it can formally adopt guidance through a governance vote and enforce this guidance throughout the system via the [validators](/get-started/resources/glossary/#validator). It is intended to function as follows:
##### Step 1
Think Tanks provide guidance.
##### Step 2
The [TTG](/get-started/resources/glossary/#ttg) adopts or rejects this guidance by voting on a hash of the public document containing it.
##### Step 3
Permissioned Actors adjust their behavior accordingly.
##### Step 4
[Validators](/get-started/resources/glossary/#validator) withhold signatures, Cancel Propose IDs, or Freeze [Minters](/get-started/resources/glossary/#minter) who have not adjusted their behavior to be in line with the guidance. If there is still non-compliance, the [TTG](/get-started/resources/glossary/#ttg) can remove errant actors from the system.
This combination of steps creates the “guidance feedback loop.”
:::success
**Example**
Think Tanks interested in the progress of the M0 ecosystem and protocol determine that only certain jurisdictions are appropriate to host [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution)s. These Think Tanks put out guidance amending the definition of an [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) to include a list of appropriate jurisdictions, and stipulate that all [Minters](/get-started/resources/glossary/#minter) should be in compliance with this list within 180 days of the proposal’s execution. The document is hashed so that all actors can validate they have the same and correct version of the guidance, and this hash is submitted to the [TTG](/get-started/resources/glossary/#ttg) under a guidance list. The [`POWER`](/get-started/resources/glossary/#power) holders will then vote on the proposal. If it passes, the guidance should be considered approved and enforceable. Once the 180 day window is complete (which is also defined in the guidance), [validators](/get-started/resources/glossary/#validator) should begin to withhold signatures on Update Collateral calls from [Minters](/get-started/resources/glossary/#minter) that attempt to include [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) held in an unapproved jurisdiction in their onchain collateral value. [validators](/get-started/resources/glossary/#validator) can also call Cancel or Freeze on a Minter that is errant in other ways not capturable in the Update Collateral method. [validators](/get-started/resources/glossary/#validator) not enforcing the approved guidance should expect to be removed by the [TTG](/get-started/resources/glossary/#ttg).
:::
### V.II Offchain Actors and Components
Key rules for eligible collateral, custody, custodians, and Minter wind-down procedures.
The following components and their associated guidance represent those contemplated for the launch of the protocol. This list is not exhaustive and will likely change over time. While much of the guidance is currently quantitative, there is nothing prohibiting qualitative guidance that is left to the interpretation of the various actors. The guidance will also include legal templates and agreements that will contain terms required to ensure smooth operation of the offchain components.
##### [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral)
A description of portfolio composition which can be placed in [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution)s and be used to generate an onchain Collateral Value, which is subsequently used in the generation of $M.
Guidance at launch: 30-90 day US Government T-bills.
##### [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution)
A description of entity structures, jurisdictions, contractual agreements, and other details that will suffice for the custody of [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral).
Guidance at launch: Orphaned SPV in approved jurisdiction(s).
##### Administrative Buffer
An amount of value that a Minter may be compelled to set aside to the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) Operator that is not included in the Minter’s [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral). This value is intended to be used by the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) Operator to facilitate the orderly wind-down of the facility should the Minter become inactive or incapacitated.
Guidance at launch: $100,000.
##### [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution) Operators
The professional corporate servicing agents (e.g. Trustees) that manage the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
Guidance at launch: Any governance-approved Operator that is technically and operationally equipped (and licensed, as applicable) to act as manager for an orphaned SPV structure in the approved jurisdiction(s) as well as capable to read and interpret the relevant onchain information.
##### Banks
The banks that hold deposits on behalf of the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
Guidance at launch: Any bank in the approved jurisdiction(s).
##### Custodians
The custodians that hold [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) (excluding bank deposits) on behalf of the [Eligible Custody Solution](/get-started/resources/glossary/#eligible-custody-solution).
Guidance at launch: Any bank in the approved jurisdiction(s) that are operationally equipped to custody the [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral).
##### Minter Wind Down Procedures
The procedures a Minter is expected to follow should they be removed from the Minter list.
Guidance at launch: [Minters](/get-started/resources/glossary/#minter) will be given a 90 day grace period after being removed from the Minter list to fully wind down their operations (a “cooperative wind down”). This means they will have [`ZERO`](/get-started/resources/glossary/#zero) remaining Owed $M. Should they fail to do so in this timeframe, the remainder of their offchain value will be forfeit including the Administrative Buffer. The Administrative Buffer, in addition to remaining offchain value in excess of Owed $M, is intended to be used to finance the various offchain actors that will be involved in the uncooperative wind down.
## I. Introduction
The core M0 protocol (which excludes [Periphery Contracts](/get-started/resources/glossary/#periphery-contract) and is hereon referred to simply as the M0 protocol) is a coordination layer for permissioned actors to generate $M.
:::note
$M is a crypto asset whose value is designed to be a robust representation of an exogenous collateral basket – a relationship enforced by the financial structure and market incentives of its generators.
:::
The purpose of $M is to become a superior building block for value representation, by combining the convenience of digital money with the risk profile of physical cash. While holders may find this construct appropriate as a vehicle for stablecoin use cases, builders, developers and financial services providers might be interested in it as raw material for the build out of novel products and services – including as collateral for stablecoins.
When cash was primarily a physical construct, it had several properties that its holders found desirable but have since been lost in the process of digitization. Physical cash is first and foremost self-custodial; it’s a bearer instrument which guarantees that it cannot be frozen or seized without due process in the holder’s jurisdiction. For example, holders in one nation do not need to be concerned that a far away government can turn off the cash in their pocket. This feature allows cash to be credibly neutral, which means that it cannot discriminate against any specific holder. Second, physical cash does not carry additional counterparty risk, it is as good as holding reserves at the issuer’s central bank. Finally, physical cash is generally fungible with itself, which is to say that except in extraordinary circumstances where holders are wary to accept a certain serial number, no bill is more or less valuable than another. The downsides of physical cash are that it cannot be transferred electronically, it must be stored in a physically safe location which becomes exponentially more difficult to secure as the quantity held rises, is becoming less broadly accepted as a means of payment, and lacks general digital properties that can allow seamless composability and programmability.
Most users have historically believed those properties to be inherited by bank deposits, as if they were merely a digital representation of cash — the fallacy of this false equivalence is becoming evident due to the stress increasingly experienced by the banking system, and is exacerbated by the global nature of stablecoins. What we call digital cash today is typically a commercial bank deposit that can be transferred electronically. It has several beneficial features such as the ability to earn interest, the ability to be transferred over the internet and across large distances, and it offers the peace of mind of digital and custodial security. It is also the most widely accepted form of payment through credit cards, debit cards, ACH, and SWIFT. Unfortunately bank deposits lose many of the desirable features of physical cash. Bank deposits are implicitly custodial and can be seized or frozen without due process in the holder’s jurisdiction – they are not credibly neutral. Due to the inherent characteristics of fractional reserve banking, bank deposits hold significant counterparty risk and are only as valuable as the specific bank’s balance sheet permits. For this reason they are also not fungible. A deposit in one bank cannot be treated as equal to a deposit in another, and thus introduces exorbitant clearing times between payments, as well as compounding complexity throughout the system.
$M is credibly neutral by design, it is by default self-custodial and fungible. Each $M is the same as every other $M and there is no ability for the protocol to discriminate against any specific holder(s). $M is stored and tracked on blockchains, and thus can be stored more securely at scale than physical cash. $M’s current instantiation is intended to be generated using short term US T-bills, representing the lowest level of counterparty risk excepting physical cash and bank reserves within the US dollar system. The T-bills used to generate $M must be held exclusively throughout a network of orphaned, bankruptcy-remote entities, which are customized to interact with the M0 protocol while meeting the formalities of the existing legal system. $M can be sent anywhere in the world instantaneously using the blockchain rails on which it exists. Interest flowing to the T-bill collateral can be partly collected by the protocol and democratized across permissioned issuers and distributors.
We refer to $M as raw material for value representation, and not necessarily a stablecoin in its own right, because the system relies on permissioned issuers (known as [Minters](/get-started/resources/glossary/#minter) in the protocol) for generation and distribution. These [Minters](/get-started/resources/glossary/#minter) should be compliant with all applicable regulations and may decide to distribute their own product, for example by wrapping the $M token in a stablecoin contract in a way that best meets their requirements. In this capacity, $M becomes a monetary building block on top of which novel products can be built.
In summary, the M0 protocol is the platform powering builders of safe, programmable, interoperable stablecoins. It introduces a superior coordination mechanism that democratizes access to the generation and management of programmable, digital cash instruments. It is an infrastructure layer not for the simplistic tokenization of real world bank deposits, but a much more sophisticated way to provide access to the liquidity on high-quality collateral. M0 intends to redesign the monetary vertical stack, rather than build an additional layer on top of what has ultimately become byzantine infrastructure.
import ZoomableImage from '@/components/ZoomableImage';
## III. Governance
The M0 protocol uses an onchain governance mechanism called a Two Token Governor
([TTG](/get-started/resources/glossary/#ttg)) to manage its various inputs. With
[TTG](/get-started/resources/glossary/#ttg), holders of the voting tokens are penalized for failing to vote.
There are two utility tokens used in the M0 [TTG](/get-started/resources/glossary/#ttg):
[`POWER`](/get-started/resources/glossary/#power) and [`ZERO`](/get-started/resources/glossary/#zero).
[`POWER`](/get-started/resources/glossary/#power) is used to vote on active proposals and can be considered the primary
management token of the mechanism. [`POWER`](/get-started/resources/glossary/#power) holders will earn
[`ZERO`](/get-started/resources/glossary/#zero) in exchange for their direct participation in governance. If a
[`POWER`](/get-started/resources/glossary/#power) holder delegates their balance to an address that is not also the
holder of the tokens, it is this address which receives the [`ZERO`](/get-started/resources/glossary/#zero) rewards.
[`ZERO`](/get-started/resources/glossary/#zero) holders are comparatively (to
[`POWER`](/get-started/resources/glossary/#power) holders) passive in the voting process and only vote on important
changes. [`ZERO`](/get-started/resources/glossary/#zero) holders at any time may Reset (see:
[`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposals) the
[`POWER`](/get-started/resources/glossary/#power) token supply to themselves.
The goal of the [TTG](/get-started/resources/glossary/#ttg) mechanism is to ensure credible neutrality of governance. In
any system there are two extremes that must be avoided: capture and fraud. In one case, the system is captured by actors
whose primary interest is not in efficient protocol operation and it ceases to function in a way where all users are
treated the same. In the other, the protocol ceases to function for anyone except the fraudulent actor. It is this
dichotomy that is at the heart of the two token design. [`POWER`](/get-started/resources/glossary/#power) holders are
treated as a managerial class that is able to earn compensation through continued benevolent participation. This
continued benevolence is judged by the [`ZERO`](/get-started/resources/glossary/#zero) holders who can always strip the
[`POWER`](/get-started/resources/glossary/#power) holders of their management rights, and thus their ability to earn
future ownership in the protocol. If the composition and decisions of [`POWER`](/get-started/resources/glossary/#power)
holders trend towards either extreme, it is in the interest of [`ZERO`](/get-started/resources/glossary/#zero) holders
to call Reset in order to restore balance.
### III.I Inputs
List of protocol inputs by the M0 Two Token Governor ([TTG](/get-started/resources/glossary/#ttg)).
The M0 [TTG](/get-started/resources/glossary/#ttg) (hereafter [TTG](/get-started/resources/glossary/#ttg)) is
responsible for the following inputs to the M0 protocol and to itself (see (Governance Controlled TTG
Parameters)\[/home/fundamentals/whitepaper/governance/#governance-controlled-ttg-parameters-1], Governance Controlled
Protocol Actors, and Section
[Governance Controlled Protocol Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters)
for further context on the actors and parameters listed below):
##### Governance Controlled TTG Parameters
* Proposal Fee
* [`POWER`](/get-started/resources/glossary/#power) Threshold
* [`ZERO`](/get-started/resources/glossary/#zero) Threshold
* CASH Toggle
##### Governance Controlled Protocol Actors
* A list of approved [Minters](/get-started/resources/glossary/#minter)
* A list of approved [validators](/get-started/resources/glossary/#validator)
* A list of Approved [Earners](/get-started/resources/glossary/#earner)
##### Governance Controlled Protocol Parameters
* [Minter Rate](/get-started/resources/glossary/#minter-rate)
* [Penalty Rate](/get-started/resources/glossary/#penalty-rate)
* [Earner Rate](/get-started/resources/glossary/#earner-rate)
* [Mint Ratio](/get-started/resources/glossary/#mint-ratio)
* [Mint Delay](/get-started/resources/glossary/#mint-delay)
* [Propose Mint Time To Live](/get-started/resources/glossary/#propose-mint-time-to-live)
* [Update Collateral Interval](/get-started/resources/glossary/#update-collateral-interval)
* Number of Signatures
* [Minter Freeze Time](/get-started/resources/glossary/#minter-freeze-time)
### III.II Operation
Protocol operational attributes.
The [TTG](/get-started/resources/glossary/#ttg) is used to vote on proposals seeking to amend the Actors and variables.
New lists and variables may be added arbitrarily over time, however the core protocol is immutable and these additions
will not directly impact its operations.
The implementation which controls the M0 protocol is deployed exclusively on the Ethereum Mainnet.
#### III.II.I Epochs
Protocol epochs for governance and proposal execution.
The mechanism practically operates in 30-day epochs, meaning in the standard operating procedure proposals are only
passed on a 30-day cycle. In practice, this broader epoch is split into two epochs of 15 days. These numbers were chosen
to align with an average calendar month.
The first epoch (the Transfer Epoch) is a non-voting period where transfers and delegation are enabled. The second
15-day epoch (the Voting Epoch) is where voting takes place on Standard Proposals and transfers and delegation are
disabled. These restrictions on onchain activity only apply to [`POWER`](/get-started/resources/glossary/#power) tokens,
there are no restrictions placed on the [`ZERO`](/get-started/resources/glossary/#zero) token.
The conceptual 30-day epoch is split into these two smaller epochs to ensure correct accounting for voting and
inflation. This means that proposals are collected in one 15-day epoch (whether it be the Voting Epoch or the Transfer
Epoch), are voted on in the following 15-day Voting Epoch, and should be executed in the following 15-day Transfer
Epoch. Proposals that passed but were not executed eventually expire. Therefore a proposal may spend as short as 15 days
plus two blocks, or as long as 45 days, from submission to execution. During the 15-day Transfer Epoch, holders may
transfer their balances, reassign delegations, and purchase [`POWER`](/get-started/resources/glossary/#power) that is
being auctioned.
#### III.II.II Proposals
Protocol change proposals for [TTG](/get-started/resources/glossary/#ttg) governance.
The [TTG](/get-started/resources/glossary/#ttg) has three types of proposals: (1) Standard Proposals, (2)
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposals, and (3)
[`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposals.
The use of threshold in the terminology represents a yes threshold, meaning that the threshold percentage of yes votes
must be reached in order for the proposal to pass. A proposal that requires a threshold never explicitly fails (although
it will eventually expire), but cannot be executed without reaching the requisite number of yes votes.
For example, if a proposal requires a 10% yes threshold, it will pass as soon as 10% of the relevant token supply has
voted yes. If this proposal never reaches 10% of the relevant token supply voting yes, it will expire without passing.
These proposal types are described next in further detail.
##### III.II.II.I Standard Proposals
Standard Proposals in [TTG](/get-started/resources/glossary/#ttg) Governance.
Standard Proposals are voteable only by [`POWER`](/get-started/resources/glossary/#power) holders and require a simple
majority of participating tokens to pass. Standard proposals do not require a threshold percentage of yes votes to pass.
If there are only 100 [`POWER`](/get-started/resources/glossary/#power) tokens voting in a Standard Proposal and 51 vote
yes while 49 vote no, it will pass. If 50 vote yes and 50 vote no, it will fail as it requires the yes balance to exceed
the no.
Standard Proposals are the only proposal type which are “mandatory” for
[`POWER`](/get-started/resources/glossary/#power) holder participation. Lack of participation will result in
[`POWER`](/get-started/resources/glossary/#power) holders being diluted in terms of their overall voting
[`POWER`](/get-started/resources/glossary/#power) in the system and will cause them to forfeit any
[`ZERO`](/get-started/resources/glossary/#zero) rewards for which they were otherwise eligible. A successful Standard
Proposal will have its Proposal Fee available to be returned to the proposer.
\:::success **Example** During a Voting Epoch a proposal to change
[Minter Rate](/get-started/resources/glossary/#minter-rate) is made. Only 10% of the
[`POWER`](/get-started/resources/glossary/#power) tokens choose to participate. When voting on the proposal, 60% of the
participating [`POWER`](/get-started/resources/glossary/#power) tokens vote yes. This proposal is passed because the yes
votes are greater than the no votes. The total number of [`POWER`](/get-started/resources/glossary/#power) holders
participating is only relevant in votes which require a [`POWER`](/get-started/resources/glossary/#power) Threshold. :::
##### III.II.II.II POWER Threshold Proposals
A [`POWER`](/get-started/resources/glossary/#power) Threshold Proposal requires a set vote threshold for immediate
execution, used for urgent or important changes in M0 Governance.
A [`POWER`](/get-started/resources/glossary/#power) Threshold Proposal can be used to submit anything which would
otherwise be a Standard Proposal, except it requires a [`POWER`](/get-started/resources/glossary/#power) Threshold and
is immediately votable and subsequently immediately executable rather than only being votable and executable in the
future epochs.
For this reason, [`POWER`](/get-started/resources/glossary/#power) holders are likely to use this proposal type in the
case of an urgent or emergency situation. If a [`POWER`](/get-started/resources/glossary/#power) Threshold has not been
reached before the proposal expires, the proposal cannot be executed. [`POWER`](/get-started/resources/glossary/#power)
Threshold Proposals expire at the end of the next epoch.
\:::success **Example** During the Transfer Period a [`POWER`](/get-started/resources/glossary/#power) Threshold Proposal
is made to change [Minter Rate](/get-started/resources/glossary/#minter-rate), which requires a
[`POWER`](/get-started/resources/glossary/#power) Threshold due to it being an
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposal. The
[`POWER`](/get-started/resources/glossary/#power) Threshold is set to 75%. The proposal becomes immediately votable and
a full 100% of the [`POWER`](/get-started/resources/glossary/#power) supply chooses to participate – 80% vote
affirmatively. This proposal has passed and is instantly executable upon the
[`POWER`](/get-started/resources/glossary/#power) Threshold being met. This is because it achieved yes votes from
greater than 75% of the [`POWER`](/get-started/resources/glossary/#power) supply (100% \* 80% = 80% yes). Note that the
proposal very well could have gone on to accumulate more yes votes, but was likely executed immediately or soon after
meeting the [`POWER`](/get-started/resources/glossary/#power) Threshold. :::
##### III.II.II.III ZERO Threshold Proposals
[`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposals allow major governance changes.
A [`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposal is used for Reset, to toggle CASH between WETH and
$M, and to set the [`POWER`](/get-started/resources/glossary/#power) and [`ZERO`](/get-started/resources/glossary/#zero)
Thresholds themselves. The Reset method is a special feature reserved for the
[`ZERO`](/get-started/resources/glossary/#zero) token holders which allows a yes threshold of
[`ZERO`](/get-started/resources/glossary/#zero) holders to change the current governor of the system to a new version
with a new [`POWER`](/get-started/resources/glossary/#power) token that is claimable pro rata to
[`ZERO`](/get-started/resources/glossary/#zero) holders or to existing [`POWER`](/get-started/resources/glossary/#power)
holders.
They do this by creating a proposal to call the Reset method. Mechanically this is affected by replacing the current
governor ([`POWER`](/get-started/resources/glossary/#power) token address) of the system with a new instance, where the
starting balance of the [`POWER`](/get-started/resources/glossary/#power) tokens are proportional to each
[`ZERO`](/get-started/resources/glossary/#zero) holder's balance in the epoch before the Reset. It is immediately
executable upon achieving a yes threshold.
It is intended for [`ZERO`](/get-started/resources/glossary/#zero) holders to use this feature should they find
something irreparably wrong with the composition and/or voting patterns of the current
[`POWER`](/get-started/resources/glossary/#power) holders and wish to take on voting responsibility themselves. It is
anticipated that [`ZERO`](/get-started/resources/glossary/#zero) holders will only Reset the governor to the existing
[`POWER`](/get-started/resources/glossary/#power) holders if the token is nearing an overflow, which will not happen for
150+ years. There is technically no limit to how many times Reset can be called, but it is not anticipated to be
frequently used if it is ever used in the first place. If a Reset is executed in the middle of a Voting Epoch, all
active and/or unexecuted proposals are effectively canceled because they are using the obsolete governor.
\:::success **Example**
The [`ZERO`](/get-started/resources/glossary/#zero) Threshold is set to 60%.
[`POWER`](/get-started/resources/glossary/#power) holders seem likely to pass a proposal to add a perceived malicious
actor to the Minter List. A user submits a proposal to Reset. This proposal is immediately votable – it achieves 70% of
the total [`ZERO`](/get-started/resources/glossary/#zero) supply voting yes. This proposal is passed and is immediately
executable. All currently votable proposals, including the proposal to add the perceived malicious Minter, are
effectively canceled.
\:::
##### III.II.II.IV Proposal Matrix
The Proposal Matrix provides a visual breakdown of the three [TTG](/get-started/resources/glossary/#ttg) proposal types.
The diagram below details the three types of proposals in the [TTG](/get-started/resources/glossary/#ttg) and highlights
which actions are associated with each type.
#### III.II.III Checkpoints and Voting
A checkpoint of balances is taken at the start of epochs and the balances contained in these checkpoints are used for
voting throughout the epoch.
During a Transfer Epoch, only the balance a user possessed at the checkpoint will be counted towards voting on
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposals, which will not include standard inflationary
proposals by definition. In order to vote the [`POWER`](/get-started/resources/glossary/#power) owner’s delegate address
(hereon referred to as the [`POWER`](/get-started/resources/glossary/#power) holder) calls the Cast Votes method on the
array of proposals they wish to vote on and specifies yes/no for each proposal.
There is no abstain option in the [TTG](/get-started/resources/glossary/#ttg).
#### III.II.IV Proposing
Making protocol change proposals for [TTG](/get-started/resources/glossary/#ttg).
Anyone with an Ethereum address and WETH or $M may submit a proposal. The [TTG](/get-started/resources/glossary/#ttg) is
to be deployed with WETH as its internal currency (known as CASH in the mechanism), and therefore any Standard Proposal
submission must pay a Proposal Fee in WETH, or at a later date $M depending on the current CASH toggle setting, in
addition to gas fees (see
[Governance Controlled TTG Parameters](/home/fundamentals/whitepaper/governance/#governance-controlled-ttg-parameters-1)).
A proposal that passes makes the Proposal Fee available to be returned to the proposer upon execution.
There are two primary structures of proposals that can be managed through the
[TTG](/get-started/resources/glossary/#ttg): (1) configuring a registrar used by the protocol, i.e. adding and removing
addresses from arbitrary lists/sets and setting arbitrary variables; (2) setting governance parameters. The M0 protocol
looks to the registrar to use certain variables and sets of addresses in its processes.
In order to propose a change to a list, a user submits a Standard Proposal or a
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposal calling the Add To List or Remove From List methods
along with the address they wish to add or remove. There is also a method called Remove From And Add To List which
facilitates swapping an address on a list. In order to add a new list to the [TTG](/get-started/resources/glossary/#ttg)
a proposer will create a proposal which uses Add To List and will specify a new list, which is created simultaneously to
the proposal being executed. Since the M0 core protocol is immutable, any list added after deployment can only be used
to manage periphery smart contracts and cannot impact core operations.
\:::success **Example** Alice wishes to add her company’s address to the list of approved
[Minters](/get-started/resources/glossary/#minter) in the M0 protocol. Alice calls Add To List, specifying the
[Minters](/get-started/resources/glossary/#minter) list along with the address she would like to gain permission to mint
$M in the protocol. :::
In order to propose a change to a configuration contract proposers call the Set Key method. In order to propose a
configuration change at the registrar, proposers create a proposal for the governor to call the registrar's Set Key
method. The update either results in the first setting or overwriting of a value for a given key (i.e. variable name).
\:::success **Example** Bob wishes to change [Minter Rate](/get-started/resources/glossary/#minter-rate) from 3% to 4%.
Bob calls Set Key and specifies the configuration contract he’d like to change along with the new value he would like it
to contain. :::
Once a proposal passes, and assuming the requisite amount of time has passed, anyone can call the Execute method to
execute the action on chain. They must pass the proposal arguments into the Execute method.
#### III.II.V Inflation Mechanics
Description of protocol inflation mechanics for [TTG](/get-started/resources/glossary/#ttg).
In each epoch the supply of [`POWER`](/get-started/resources/glossary/#power) is inflated by 10% and the supply of
[`ZERO`](/get-started/resources/glossary/#zero) is inflated by up to 5,000,000 tokens. This inflation is claimed pro
rata by participating [`POWER`](/get-started/resources/glossary/#power) holders, specifically by their delegate address.
Any [`POWER`](/get-started/resources/glossary/#power) that remains undistributed (or that could not be claimed because
the holder did not fully participate in that epoch) is auctioned off to the highest bidder in a pay-as-bid Dutch auction
(hereon “Dutch auction”).
When there are tokens to auction, the auction starts at the beginning of the Transfer Epoch and ends at the finish of
the Transfer Epoch. Therefore if a user purchases [`POWER`](/get-started/resources/glossary/#power) during an auction
they will always be able to use those tokens to vote in the subsequent Voting Epoch. Each participating
[`POWER`](/get-started/resources/glossary/#power) holder during the Voting Epoch will also receive their pro rata (based
on their percentage of total voting power) share of the 5,000,000 [`ZERO`](/get-started/resources/glossary/#zero)
tokens.
Once a Standard Proposal has been submitted it can be voted on in the following Voting Epoch, unless it is a
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposal or a
[`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposal, which can be voted on at any time. When a proposal
becomes available for voting, it is mandatory for [`POWER`](/get-started/resources/glossary/#power) holders to vote on
it or else the owner of the [`POWER`](/get-started/resources/glossary/#power) tokens will lose relative voting weight in
the system. If a [`POWER`](/get-started/resources/glossary/#power) owner’s delegate fails to vote on any proposal in an
epoch, they will forfeit any [`POWER`](/get-started/resources/glossary/#power) or
[`ZERO`](/get-started/resources/glossary/#zero) inflation they would have otherwise been able to claim.
[`POWER`](/get-started/resources/glossary/#power) holders must vote on all Standard Proposals in the Voting Epoch;
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposals and
[`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposals do not factor into
[`POWER`](/get-started/resources/glossary/#power) inflation dynamics. There is no inflation if an epoch only has
[`POWER`](/get-started/resources/glossary/#power) Threshold Proposals and / or
[`ZERO`](/get-started/resources/glossary/#zero) Threshold Proposals, or no proposals at all.
\:::success **Example** Alice is a [`POWER`](/get-started/resources/glossary/#power) holder with 1,000
[`POWER`](/get-started/resources/glossary/#power) tokens, the total [`POWER`](/get-started/resources/glossary/#power)
supply is 10,000; hence Alice controls 10% of the [`POWER`](/get-started/resources/glossary/#power) voting weight. In
epoch 1 she participates fully by voting on all Standard Proposals. Simultaneous to participating, Alice claims an
additional 100 [`POWER`](/get-started/resources/glossary/#power) tokens and 500,000
[`ZERO`](/get-started/resources/glossary/#zero) tokens, i.e. 10% of a [`POWER`](/get-started/resources/glossary/#power)
inflation of 1,000 tokens and 10% of a [`ZERO`](/get-started/resources/glossary/#zero) inflation of 5,000,000 tokens. In
epoch 2 Alice fails to vote on a Standard Proposal and is therefore not able to claim any
[`POWER`](/get-started/resources/glossary/#power) or [`ZERO`](/get-started/resources/glossary/#zero) tokens. The 110
[`POWER`](/get-started/resources/glossary/#power) tokens (i.e. 10% of the new additional
[`POWER`](/get-started/resources/glossary/#power) supply of 1,100 tokens) that should have gone to Alice are auctioned
in the Dutch auction. Bob buys these 110 tokens for 1 WETH (the internal currency of the
[TTG](/get-started/resources/glossary/#ttg)) and can now participate in the following voting epoch. The
[`ZERO`](/get-started/resources/glossary/#zero) tokens that should have gone to Alice are simply never minted. :::
#### III.II.VI Dutch Auction
Dutch Auction for [`POWER`](/get-started/resources/glossary/#power) tokens.
Once the Transfer Epoch has begun the Dutch auction will begin simultaneously if any
[`POWER`](/get-started/resources/glossary/#power) holder failed to participate. The price per basis point (0.01%) of
[`POWER`](/get-started/resources/glossary/#power) token, calculated as a percentage of the total
[`POWER`](/get-started/resources/glossary/#power) supply, in the Dutch auction will start at 2^99 wei (the smallest unit
of WETH) and decrement the exponent approximately every 3.6 hours. During the period between exponent decreases the
price linearly declines. This means that after the first 3.6 hours of the auction the price will be 2^98 wei and
linearly decrease to 2^97 wei over the following 3.6 hours.
In the implementation, bitwise shifting is used to achieve this effect. That is to say that at the midpoint between two
exponents, the value is halfway between them. In order to purchase [`POWER`](/get-started/resources/glossary/#power) in
the Dutch auction, purchasers must call the Buy method.
The diagrams below illustrate the intended price curve of the Dutch auction in ETH over the 15 day period. The first
demonstrates the entire price curve, while the second shows a smaller slice of the curve to demonstrate its
semi-linearity.
#### III.II.VII Delegation
Delegation of voting [`POWER`](/get-started/resources/glossary/#power) for [TTG](/get-started/resources/glossary/#ttg).
Both [`POWER`](/get-started/resources/glossary/#power) and [`ZERO`](/get-started/resources/glossary/#zero) owners may
delegate their voting [`POWER`](/get-started/resources/glossary/#power) to an arbitrary Ethereum address during the
Transfer Epoch. Delegated [`POWER`](/get-started/resources/glossary/#power) will retain its inflation in the owner
address, while [`ZERO`](/get-started/resources/glossary/#zero) rewards will be claimable by the delegate address.
Owning [`ZERO`](/get-started/resources/glossary/#zero) does not earn the owner or the owner’s delegate inflation or
rewards aside from $M generated by the protocol fees, and thus delegation does not have any impact on the owner outside
of transferring voting power. [`ZERO`](/get-started/resources/glossary/#zero) does earn fees from Proposal Fees on
failed Standard Proposals, the payments from [`POWER`](/get-started/resources/glossary/#power) token auctions and a
portion of [Minter Rate](/get-started/resources/glossary/#minter-rate) and
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) charges to
[Minters](/get-started/resources/glossary/#minter).
Delegation snapshots are taken at the beginning of the epoch and close at the end of the epoch, and the values in the
snapshots are subject to change until the epoch closes. Both [`POWER`](/get-started/resources/glossary/#power) and
[`ZERO`](/get-started/resources/glossary/#zero) follow the ERC20 standard and holders must call the Delegate method and
provide the address they wish to delegate to. For holders that do not actively Delegate, the default delegation is set
to the address which owns the tokens. Users do not need to alter their delegation in each epoch unless they wish to
change delegates.
#### III.II.VIII ZERO Claiming of Residual Value
In exchange for [`ZERO`](/get-started/resources/glossary/#zero) holders' participation in protocol governance, they will
receive the remainder of the protocol fees. The anticipated accumulation of tokens to the
[`ZERO`](/get-started/resources/glossary/#zero) holders are Proposal Fee payments from rejected proposal submission, the
payments from [`POWER`](/get-started/resources/glossary/#power) token auctions, and a portion of
[Minter Rate](/get-started/resources/glossary/#minter-rate) and
[Penalty Rate](/get-started/resources/glossary/#penalty-rate) charges to
[Minters](/get-started/resources/glossary/#minter) (see
[Protocol Fees](/home/fundamentals/whitepaper/protocol/#protocol-fees)).
Proposal Fee and auction payments are collected in WETH or $M, depending on the status of the CASH toggle, and
[Minter Rate](/get-started/resources/glossary/#minter-rate) is collected in $M.
At any time a [`ZERO`](/get-started/resources/glossary/#zero) holder may call the Claim method in order to claim this
accumulated value. The amount of claimable tokens are pro rata to each account's
[`ZERO`](/get-started/resources/glossary/#zero) balance on the close of each epoch they are trying to claim for. They
pass the array of epochs (or a starting epoch and ending epoch) they are seeking to claim for as arguments to the
method.
### III.III Governance Controlled TTG Parameters
List of Governance controlled [TTG](/get-started/resources/glossary/#ttg) parameters.
##### CASH
The internal currency of the [TTG](/get-started/resources/glossary/#ttg). It is used to pay Proposal Fee and to purchase
[`POWER`](/get-started/resources/glossary/#power) in the Dutch auction. It can be toggled between WETH and $M.
Logic: This token must be permissionless and well distributed in order to prevent takeover of the
[TTG](/get-started/resources/glossary/#ttg). It should also have sufficient value to its holders in order to deter spam
and to increase the efficiency of the Dutch auction.
##### Proposal Fee
The amount paid in CASH to submit any proposal. It is alterable with a Standard Proposal.
Logic: This amount should be sufficiently high to deter spam, but not so high as to deter legitimate proposals.
##### POWER Threshold
The number of yes votes as a percentage of the total [`POWER`](/get-started/resources/glossary/#power) supply required
to pass proposals which require a [`POWER`](/get-started/resources/glossary/#power) Threshold.
Logic: This percentage should be low enough to ensure that in an emergency situation, enough
[`POWER`](/get-started/resources/glossary/#power) holders can be collected to pass a proposal. It should be high enough
that a malicious proposal cannot be passed instantly.
\:::success **Example**
[`POWER`](/get-started/resources/glossary/#power) Threshold = 80%. Therefore if there are 1,000,000 total
[`POWER`](/get-started/resources/glossary/#power) in existence, 800,000
[`POWER`](/get-started/resources/glossary/#power) will need to vote affirmatively for a proposal to pass that requires a
[`POWER`](/get-started/resources/glossary/#power) Threshold. :::
##### ZERO Threshold
The number of yes votes as a percentage of the total [`ZERO`](/get-started/resources/glossary/#zero) supply required to
pass proposals which require a [`ZERO`](/get-started/resources/glossary/#zero) Threshold.
Logic: This percentage should be low enough that it is possible to call Reset if necessary. It should be high enough to
ensure that Reset is not called without a very high level of consensus.
\:::success **Example**
[`ZERO`](/get-started/resources/glossary/#zero) Threshold = 60%. Therefore if there are 1,000,000,000 total
[`ZERO`](/get-started/resources/glossary/#zero) in existence, 600,000,000
[`ZERO`](/get-started/resources/glossary/#zero) will need to vote affirmatively for a proposal to pass that requires a
[`POWER`](/get-started/resources/glossary/#power) Threshold. :::
### III.IV Immutable [TTG](/get-started/resources/glossary/#ttg) Parameters
List of immutable [TTG](/get-started/resources/glossary/#ttg) parameters.
##### Epoch Duration
The combined length of the Voting Epoch and the Transfer Epoch. Set to 30 days.
Logic: This amount of time should be short enough to permit for timely management of the protocol, but long enough for
all [`POWER`](/get-started/resources/glossary/#power) holders to both socialize and physically vote on Standard
Proposals.
##### Voting Epoch Duration
The length of the Voting Epoch. Set to 15 days.
Logic: This amount of time should be long enough to permit [`POWER`](/get-started/resources/glossary/#power) holders to
physically exercise their vote.
##### Transfer Epoch Duration
The length of the Transfer Epoch. Set to 15 days.
Logic: This amount of time should be long enough to contain the Dutch auction and for any
[`POWER`](/get-started/resources/glossary/#power) holders that may wish to perform transfers or re-delegations to do so.
##### Auction Duration
The length of the Dutch auction for unclaimed [`POWER`](/get-started/resources/glossary/#power) inflation. Set to 15
days and overlaps perfectly with the Transfer Epoch.
Logic: This amount of time should be long enough to cross all conceivable prices while still promoting efficient price
discovery. Note that this length matches the Transfer Epoch, so that tokens acquired in the Transfer Epoch will be
included in the checkpoint for the following Voting Epoch.
##### Dutch Auction Exponent
The exponent with a base of 2 that determines the starting auction price. Set to 99.
Logic: This number should produce a sufficiently high starting price per
[`POWER`](/get-started/resources/glossary/#power) token such that the market price of
[`POWER`](/get-started/resources/glossary/#power) in Cash is never above this price. It should not be so high as to
cause the auction to exceed the Transfer Epoch before reaching 0 given the Dutch Auction Period Time setting.
##### Dutch Auction Periods
The number of equal periods that must fit into the Transfer Epoch. Set to 100.
Logic: This number of periods defines how often the Dutch auction will decrease the Dutch Auction Exponent. At the
current setting the Dutch auction will decrement the Dutch Auction Exponent approximately every 3.6 hours.
### III.V Immutable POWER Parameters
List of immutable [`POWER`](/get-started/resources/glossary/#power) parameters.
##### POWER Initial Supply
The initial supply of [`POWER`](/get-started/resources/glossary/#power) tokens before any inflation. Set to 1,000,000.
Decimals are 0.
Logic: The initial supply of [`POWER`](/get-started/resources/glossary/#power) should be sufficient to distribute to the
initial holders in the network, but not so high as to cause a premature overflow error. The lack of decimals (and thus
lack of subdivision of tokens) was also chosen for this reason. See the diagram under
[`POWER`](/get-started/resources/glossary/#power) Inflator for further analysis. Another factor to consider in this
initial supply is the “dust level,” meaning the level at which in a Reset a
[`ZERO`](/get-started/resources/glossary/#zero) holder would not receive any
[`POWER`](/get-started/resources/glossary/#power) tokens. For example, since new
[`POWER`](/get-started/resources/glossary/#power) (post-Reset) is based on existing
[`ZERO`](/get-started/resources/glossary/#zero) balances, anyone who owns less than 1 /
[`POWER`](/get-started/resources/glossary/#power) Initial Supply of a [`ZERO`](/get-started/resources/glossary/#zero)
token will get 0 [`POWER`](/get-started/resources/glossary/#power) after a Reset.
##### POWER Inflator
The percentage inflation of the [`POWER`](/get-started/resources/glossary/#power) supply per active epoch. This occurs
only in epochs with a votable proposal, hence why they are referred to as active. If a
[`POWER`](/get-started/resources/glossary/#power) holder fails to fully participate in an epoch with at least one
votable, their balance of [`POWER`](/get-started/resources/glossary/#power) tokens will not decrease but their
percentage of overall voting [`POWER`](/get-started/resources/glossary/#power) will decrease by 1 / (1 +
[`POWER`](/get-started/resources/glossary/#power) Inflator). Set to 10%.
Logic: This percentage must sufficiently encourage [`POWER`](/get-started/resources/glossary/#power) holder
participation without occasional lapses in participation destabilizing the system. At 10% inflation, a
[`POWER`](/get-started/resources/glossary/#power) holder can expect to lose \~45% of their voting
[`POWER`](/get-started/resources/glossary/#power) if not participating for 6 epochs (note that epochs in our
implementation correspond roughly to calendar months), \~70% of their voting
[`POWER`](/get-started/resources/glossary/#power) if not participating for 12 epochs, and \~90% of their voting
[`POWER`](/get-started/resources/glossary/#power) if not participating for 24 epochs. To a lesser extent this number
should also take into account a realistic number of epochs with votable proposals to ensure that the operation of the
protocol is not impacted by an overflow error. This would require either a Reset or a Hard Fork. The following diagrams
demonstrate the intended impact of the [`POWER`](/get-started/resources/glossary/#power) Inflator on an inactive
participant, and a comparison of [`POWER`](/get-started/resources/glossary/#power) Inflator and decimal setting as they
impact overflow times assuming a 30-day total epoch time.
Voting [`POWER`](/get-started/resources/glossary/#power) as a percentage of starting voting
[`POWER`](/get-started/resources/glossary/#power) (y-axis) over missed epochs with a votable proposal (x-axis), using a
10% [`POWER`](/get-started/resources/glossary/#power) Inflator
Years to overflow assuming 12 epochs per year with votable proposals. Comparison of
[`POWER`](/get-started/resources/glossary/#power) Inflator (left column) and decimal choice for
[`POWER`](/get-started/resources/glossary/#power) token (top row). Assumes a starting
[`POWER`](/get-started/resources/glossary/#power) supply of 1,000,000.
### III.VI Immutable ZERO Parameters
List of immutable [`ZERO`](/get-started/resources/glossary/#zero) parameters.
##### ZERO Initial Supply
The initial supply of the [`ZERO`](/get-started/resources/glossary/#zero) token before any rewards. Set to
1,000,000,000. Decimals are 6.
Logic: The initial supply of [`ZERO`](/get-started/resources/glossary/#zero) should be large enough to promote a high
level of decentralization (as it pertains to the Reset method), and small enough to not break common integrations.
##### ZERO Reward
The maximum amount of [`ZERO`](/get-started/resources/glossary/#zero) given to
[`POWER`](/get-started/resources/glossary/#power) holders in a Voting Epoch. Each
[`POWER`](/get-started/resources/glossary/#power) holder is given their pro rata share of the reward if they fully
participate and the tokens are claimed simultaneously to voting. Tokens which go unclaimed are never minted. No tokens
are distributed in an epoch with no active proposals. Set to 5,000,000.
Logic: The reward amount should provide enough incentive to Standard Proposal voters to consistently vote while not
destabilizing the protocol through unpredictable distribution. An additional consideration is the maximum level of
decentralization that can be achieved given average gas fees – i.e. the reward must at least cover the average gas fee
of participants and thus the average gas fee puts a practical boundary on the percentage of
[`POWER`](/get-started/resources/glossary/#power) one must own to justify participation.
\:::success Example
Alice has 10% of the [`POWER`](/get-started/resources/glossary/#power) supply delegated to her. She fully participates
in a Voting Epoch and is rewarded with 10% of the reward, in this case 500,000
[`ZERO`](/get-started/resources/glossary/#zero) tokens. :::
See the diagrams below for further illustration of the potential impact of the reward on the
[`ZERO`](/get-started/resources/glossary/#zero) supply over time.
import ZoomableImage from '@/components/ZoomableImage';
## IV. The M0 Economy
##### High-level description of the M0 economy.
The M0 protocol is a coordination mechanism. It is not intended to replace existing financial actors, but rather to
provide novel and more efficient means by which they can interact. We believe that a universal blockchain-based
protocol, where rules and transparency are enforced by code, is superior to the feudal and opaque landscape of value
transmission present today.
The M0 Protocol is intended to coordinate [Minters](/get-started/resources/glossary/#minter),
[validators](/get-started/resources/glossary/#validator), and [Earners](/get-started/resources/glossary/#earner). It is
anticipated that offchain this may correspond to financial services providers (such as stablecoin issuers), auditors,
and institutional holders of $M.
### IV.I Minters
##### What are [Minters](/get-started/resources/glossary/#minter) in the context of M0?
[Minters](/get-started/resources/glossary/#minter) are primarily incentivized to join the protocol because they want to
earn the spread between the yield (net of expenses) on their
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) and the protocol’s
[Minter Rate](/get-started/resources/glossary/#minter-rate). In addition, the
[Mint Ratio](/get-started/resources/glossary/#mint-ratio) will determine the attractiveness of Minting relative to the
yield spread.
The effective ROC (return on capital) of a Minter is net yield generated on the
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral), divided by the net cash investment, which
is the capital invested into [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral), minus their
Owed $M (assuming they were able to sell this $M at $1) plus the Administrative Buffer.
It is anticipated that [Minters](/get-started/resources/glossary/#minter) in the M0 protocol will correspond to
financial services providers (such as stablecoin issuers) offchain. The ultimate function of the Minter is the
generation and management of the supply of $M.
Considering the initial [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) is intended to be
short term T-bills, it is assumed that the [Minter Rate](/get-started/resources/glossary/#minter-rate) will need to be
less than the US Federal Funds rate. See the diagram below for a visual example of the basic Minter economics.
It is also anticipated that [Minters](/get-started/resources/glossary/#minter) will engage in arbitrage. If $M is
trading above $1 on secondary markets, it is logical for [Minters](/get-started/resources/glossary/#minter) to deposit
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) in order to generate more $M This will boost
their net yield. Conversely, if $M is trading below $1 on secondary markets, it is logical for
[Minters](/get-started/resources/glossary/#minter) to repurchase $M and to use it to Retrieve
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral). This will also boost their net yield. It is
for this reason that $M is expected to trade with some volatility around (US) $1 – the mechanism relies on sometimes
inefficient and unpredictable market forces to achieve an average price of $1 over the long term.
There will be no built in mechanism at the protocol level to ensure the price stability of $M; rather, that will be
achieved via the economic incentives described and visualized above.
### IV.II Validators
##### What are [validators](/get-started/resources/glossary/#validator) in the context of M0?
Economically, all [validators](/get-started/resources/glossary/#validator) must be incentivized offchain or use
periphery smart contracts. There is no Validator compensation in the core protocol. This decision was made because the
Validator landscape is complex and the chance of accurately encapsulating these complex economic arrangements onchain
was nil.
For the basic function of providing signatures for Update Collateral, it is expected that
[validators](/get-started/resources/glossary/#validator) and [Minters](/get-started/resources/glossary/#minter) will
enter into binding, offchain legal agreements specifying applicable terms and appropriate compensation. It is
anticipated that [validators](/get-started/resources/glossary/#validator) in the M0 protocol will eventually correspond
to auditors offchain. The ultimate function of the Validator is to provide as close to real time attestation of the
[Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) being used to generate $M as possible.
While the protocol considers all Validator addresses to be fungible, there are in fact many specializations that could
occur offchain in the Validator ecosystem. For instance, there may be
[validators](/get-started/resources/glossary/#validator) that specialize in signing off on onchain Collateral Value
updates, while others act as “sentinels” and exclusively exist to call Cancel and Freeze on errant
[Minters](/get-started/resources/glossary/#minter). The level of specialization could even go beyond the initial methods
of the protocol as [Periphery Contract](/get-started/resources/glossary/#periphery-contract)s are added by the
ecosystem.
### IV.III Earners
##### What are [Earners](/get-started/resources/glossary/#earner) in the context of M0?
[Earners](/get-started/resources/glossary/#earner) are simply addresses approved by the
[TTG](/get-started/resources/glossary/#ttg) to earn the [Earner Rate](/get-started/resources/glossary/#earner-rate). It
is expected that, throughout the cycle, the [Earner Rate](/get-started/resources/glossary/#earner-rate) will remain
comparable to the US Federal Funds rate as well in order to entice [Earners](/get-started/resources/glossary/#earner) to
continue to hold $M.
The [Earner Rate](/get-started/resources/glossary/#earner-rate) can be used as an additional tool to encourage $M price
stability around $1. If the price of $M is above $1, the [TTG](/get-started/resources/glossary/#ttg) can lower the
[Earner Rate](/get-started/resources/glossary/#earner-rate) in order to discourage holding of $M and to encourage
selling of $M for alternative sources of yield. If the price of $M is below $1, the
[TTG](/get-started/resources/glossary/#ttg) can raise the [Earner Rate](/get-started/resources/glossary/#earner-rate) in
order to encourage the holding and purchase of $M. It should be noted that the
[Earner Rate](/get-started/resources/glossary/#earner-rate) can be higher than the
[Minter Rate](/get-started/resources/glossary/#minter-rate) as long as the amount of $M being paid out via the
[Earner Rate](/get-started/resources/glossary/#earner-rate) is less than the amount of total $M generated from
[Minter Rate](/get-started/resources/glossary/#minter-rate).
It is anticipated that [Earners](/get-started/resources/glossary/#earner) in the M0 protocol will correspond to
institutional holders of $M offchain, and to issuers and distributors that maintain $M inventory. The ultimate function
of [Earners](/get-started/resources/glossary/#earner) is as a source of demand for $M, making it more likely that
[Minters](/get-started/resources/glossary/#minter) can efficiently generate $M. This is effectively to say that
[Earners](/get-started/resources/glossary/#earner) align nicely with the ultimate distributors of $M to the broader
market.
\:::success **Example**
The [TTG](/get-started/resources/glossary/#ttg) permissions a large cryptocurrency exchange to the
[Earners](/get-started/resources/glossary/#earner) list. This exchange has millions of users and is regulated/licensed
appropriately in the jurisdiction(s) it serves. Once permissioned, the exchange can use customer’s $M to earn the
[Earner Rate](/get-started/resources/glossary/#earner-rate). They can now pass a portion or all of the
[Earner Rate](/get-started/resources/glossary/#earner-rate) on to their customers. :::
## Abstract
The core M0 protocol is a coordination layer for permissioned institutional actors to generate $M.
$M is a fungible token that can be generated by locking [Eligible Collateral](/get-started/resources/glossary/#eligible-collateral) in a secure offchain facility. The protocol enforces a common set of rules and safety procedures for the management of $M.
## Adopted Guidance v1.50
M0's Adopted Guidance outlines the core rules of engagement outside of the enforceable domain of the protocol smart contracts for the various actors in the M0 ecosystem.
* **Document Version**: 1.50
* **Last updated**: August 5, 2025
* **Document hash**: f171a7c05bdd1e39a14d4799deda12f632195a64014b49688f10c5e3a7a7c28f (can be verified against the [PDF version](https://github.com/m0-foundation/adopted-guidance/blob/main/m0_adopted_guidance_v1.50.pdf) of this document)
* **Governance approval**: see [here](https://governance.m0.org/proposal/98142676326281053231530662417190301636164164389599519553602654350153003684715).
## 5. Validators
### 5.1. Contact Information of Currently Permissioned Validators
:::note
Validator One GmbH
Friedrichstr. 114A, 10117 Berlin, Germany
[contact@validator-one.com](mailto\:contact@validator-one.com)
[www.validator-one.com](http://www.validator-one.com)
Public Key: 0xEF1D05E206Af8103619DF7Cb576068e11Fd07270
:::
:::note
Chronicle Labs
190 Elgin Ave, George Town Cayman KY1-9005, Cayman Islands
[hello@chroniclelabs.org](mailto\:hello@chroniclelabs.org)
[www.chroniclelabs.org](http://www.chroniclelabs.org)
Public Key: 0xEe4d4938296E3BD4cD166b9b35EE1B8FeD2F93C1
:::
### 5.2. Eligibility Criteria for Permissioned Validators
Validators shall fulfill the following minimum requirements:
* Be a duly incorporated entity, not in a
[High-Risk Jurisdiction](/home/fundamentals/adopted-guidance/ecosystem/#high-risk-jurisdictions).
* Have minimum affiliation (via its shareholders) with or (practically) control by any Minter and/or SPV Operator (or
the affiliates of those) that is in direct contractual relationship with for the provision of its services. To the
extent that the Validator has overlapping stakeholders with other Actors within the ecosystem, it is the duty of the
Validator to provide evidence of appropriate corporate governance.
* Obtain and maintain all necessary authorizations and consents for the performance of its obligations under the
Mandatory Contracts it needs to enter into with other ecosystem participants according to the Adopted Guidance.
* Have technical visibility of the Collateral Storage, and specifically the ability to connect to the SPVs custody
accounts, e.g. via API, direct observation of a distributed ledger, or other technical means, as well as to exchange
Signatures with the Minter.
* Have all applicable licenses and permissions in their jurisdiction.
### 5.3. Obligations of Validators
Validators provide Signatures to Minters for the update of their Collateral Balance and for the removal of RetrievalIDs.
This requires the Validators to have full visibility on:
* The Minter’s Collateral Storage, including its Deposit Accounts.
* All order flows of such accounts.
* The appropriate blockchain addresses of a Minter’s Collateral Storage.
In addition to verifying account data and compliance with the eligibility criteria for collateral, the Validators shall
perform checks, given that it is in the interest of the Minter to provide maximum transparency on the collateral.
Validators shall, for example, confirm that the Mandatory Contracts are in place between the Minter and the SPV
Operator, as well as confirm that the country of incorporation of the SPV is in an Approved Jurisdiction at the time of
each confirmation, etc.
In the case that the Validator gains certain knowledge that certain Mandatory Contracts have been made invalid or have
been terminated without a valid replacement, the Validator shall no longer provide Signatures until those contracts have
been remedied in compliance with the Adopted Guidance.
Should the Validator perceive obviously suspicious or obviously non-compliant mint proposals as well as mint proposals
that are the result of obvious human error (by any Minter) it shall cancel such mint proposals within the Mint Delay
time frame.
The Validator shall in particular cancel mint proposals of Minters if the Validator was informed by the SPV Operator of
such Minter that the Administrative Buffer is not meeting the criteria set out in the Adopted Guidance for as long as
such breach exists.
In emergency situations where a Validator has a justifiable reason to believe that a Minter is non-compliant with the
Adopted Guidance in a material way, but does not refrain from submitting mint proposals, or a damage to the
collateralization of $M is to be reasonably expected, the Validator shall use the freezeMinter() function until the
situation is remedied.
The use of the freezeMinter() and the cancelMint() functions shall not constitute any basis for claims for damage
compensation by the affected Minter and/or its BD Minter against the Validator who has called such function.
Validators shall only terminate a Minter-Validator Agreement with a notice period of no shorter than 90 days, including
in the case of a Wind Down. They shall help the Minter to transition to a replacement Validator. If no replacement
Validator can be found within this time window, the terminating Validator shall extend its service for the Minter for up
to an additional 90 days upon request of the Minter. The right to terminate for cause shall remain unaffected.
### 5.4. Guidelines for Submission of Permissioning Requests
An application for the permissioning as a Validator requires the whitelisting of the applicant’s public key on the list
of permissioned Validators. Before submitting its application, the Validator shall make a KYC report public via
appropriate channels, as well as appropriate proof that the requirements set forth in
[Eligibility Criteria for Permissioned Validators are met](/home/fundamentals/adopted-guidance/validators/#eligibility-criteria-for-permissioned-validators):
* Certified copies of the commercial register and/or trade register and/or register of companies or alike, proving that
the company was duly incorporated in the jurisdiction it is providing its services from.
* Proof of who the ultimate beneficial owners (UBO) of the company are.
* Certified copies of all required official licenses required to operate the business and to provide the services
outlined in the Adopted Guidance and in the contractual agreements where the Validator is a party to.
* A legally binding declaration that no insolvency, bankruptcy or similar/comparable proceedings are currently pending
or to be anticipated in the foreseeable future in relation to the company.
* Proof that the company is minimally affiliated (via its shareholders) with or (practically) controlled by any SPV
Operator and/or Minter the Validator is in direct contractual relationship with for the provision of its service. When
there is some level of affiliation, relevant evidence of appropriate corporate governance shall be presented.
* A legally binding declaration, as long as objectively and legally possible, to set up its business activities in
regards to the services and contractual relationships as outlined in the Adopted Guidance.
* A legally binding commitment, as long as objectively and legally possible, to amend its contractual relationships
reflecting potential mandatory changes of the Adopted Guidance.
Where confidentiality concerns emerge the M0 Foundation can step under Non-Disclosure Agreements to analyze the required
documentation and provide a qualified public opinion to the ecosystem.
import ZoomableImage from '@/components/ZoomableImage';
## 4. SPV Operators
An SPV Operator is the Actor managing the portfolio of collateral in the SPV on behalf of the SPV but for the benefit of
a Minter’s business operation. The SPV Operator also acts as selling agent in the context of a wind down (as described
in
[Obligations Outside of the Normal Course of Business](/home/fundamentals/adopted-guidance/spv-operators/#obligations-outside-of-the-normal-course-of-business))
and can even wind down a Minter in cases where the Minter is unable or unwilling to comply with the Protocol rules.
Adopted Guidance indicates the entities that should be considered as appropriate to act as SPV Operators in the M0
ecosystem.
### 4.1. Contact Information of Currently Approved SPV Operators
:::note
CrossLend GmbH
Leipziger Str. 124, 10117 Berlin, Germany
[operations@crosslend.com](mailto\:operations@crosslend.com)
[www.crosslend.com](http://www.crosslend.com)
:::
### 4.2. Eligibility Criteria for Approved SPV Operators
SPV Operators shall fulfill the following minimum requirements:
* Be a duly incorporated entity in an
[Approved Jurisdiction](/home/fundamentals/adopted-guidance/eligible-collateral/#approved-jurisdictions).
* Minimum affiliation (via its shareholders) with or (practically) control by any Validator and/or Minter, or the
affiliates of any Validator or Minter. To the extent that the SPV Operator has overlapping stakeholders with other
Actors within the ecosystem, it is the duty of the SPV Operator to provide evidence of appropriate corporate
governance and liability separation in dealing with collateral.
* It has obtained and maintained all necessary licenses, authorisations and consents for the performance of its
obligations under the mandatory agreements it needs to enter into with other ecosystem participants according to the
Adopted Guidance.
* It maintains a contractual relationship with the SPV that under no circumstance endangers the status of that SPV to be
considered as such in its Approved Jurisdiction, as outlined in
[Approved Jurisdictions](/home/fundamentals/adopted-guidance/eligible-collateral/#approved-jurisdictions).
* It has meaningful equity to sustain its business.
* It has developed and can provide upon request a business plan for the following 3 years of operations.
### 4.3. Obligations of SPV Operators
SPV Operators are expected to enter into a Minter-SPV Operator Agreement with every Minter they provide services to.
Today, these agreements are considered part of the Mandatory Contracts as defined in the Adopted Guidance. In case of
any change to the Mandatory Contracts, the Minter and SPV Operator, as any other Actor, are expected to amend the
Minter-SPV Operator Agreement accordingly without delay.
The mandate of the SPV Operator to manage the collateral is intended to have the main objective be to protect the
stability of $M under all possible circumstances. For this reason, the Adopted Guidance suggests prudence in adopting
technological innovation at this level of the stack.
To that end, the duties of the SPV Operator can be divided into two groups:
* Obligations in the Normal Course of Business
* [Obligations Outside of the Normal Course of Business](/home/fundamentals/adopted-guidance/spv-operators/#obligations-outside-of-the-normal-course-of-business)
The Normal Course of Business generally refers to the scenario where the Minter operates in accordance with the Protocol
rules and is fully capable of doing so. As a consequence, outside the Normal Course of Business refers to all scenarios
where the Minter is, for example, non-compliant with the rules of the Protocol, enters into insolvency, or is
incapacitated or unwilling to fulfill its obligations for other reasons.
#### 4.3.1. Obligations in the Normal Course of Business
In the Normal Course of Business the SPV Operator shall manage the Collateral Storage in accordance with the Adopted
Guidance and all applicable laws and regulations.
To that extent, the SPV Operator shall use most or all available financial resources (including Deposit Equivalents) to
purchase Eligible Collateral. Some flexibility on reinvestment should be provided to the SPV Operator in order to timely
fulfill existing or foreseen Retrieval Requests. It shall do so whenever available financial resources are existing in
the Collateral Storage unless such available financial resources are part of a Retrieval Request or the amount is
economically negligible. The following chart shows an example of the replenishment of Eligible Collateral in the Normal
Course of Business:
1. Any liquidity event affecting any collateral instrument (e.g. upon maturity of the instrument or as regular interest
payment or other cashflow) is paid within the Collateral Storage, or more specifically to the custody accounts of
the SPV.
2. Without delay, the SPV Operator initiates the purchase of new Eligible Collateral. This means that the amount of
collateral, all other factors unchanged, will grow over time. The only way for the Minter to extract collateral is
via the Retrieval Process.
The SPV Operator shall also cooperate with the Minter to execute Retrieval Processes. When a Minter requests a retrieval
of collateral with the SPV Operator, the SPV Operator shall verify that a respective RetrieveID is existing in the
Protocol. Once verified, the SPV Operator shall timely sell a corresponding amount of collateral and/or use liquidity
from maturity collateral maturing at the same day and transfer the amount requested to be retrieved to the Minter.
#### 4.3.2. Obligations Outside of the Normal Course of Business
The interruption of a Minter’s Normal Course of Business, due to a diverse set of circumstances that the Adopted
Guidance intends to address, should immediately or within a realistic timeframe lead to a Wind Down of such a Minter.
Minter De-Permissioning refers to any event resulting in a successful Governance vote to remove a Minter from the list
of Permissioned Minters. Permissioned Minters are any entities in the M0 ecosystem that have been permissioned by
Governance to mint $M The Minter De-Permissioning is the main smart-contract enforced way for a Minter to leave the
Normal Course of Business and enter a Wind Down process. A Wind Down is the process of progressively terminating a
Minter’s operations by the SPV Operator, who sells or otherwise realizes the Eligible Collateral, using all available
financial resources to purchase $M in the market, and burning such $M in repayment of the Minter's Owed $M Depending on
the scenario, such a Wind Down will be done either in an amicable way – i.e. the Amicable Wind Down, or under the full
control of the SPV Operator – i.e. the Non-Amicable Wind Down. The intention of this staged process is to give the
ability to a Minter in good faith with the ability to remedy its actions and provide an orderly execution of its exit
process, while maximizing funds recovery in the interest of the protocol.
In other exceptional cases in which a Minter is incapable of redeeming, a Minter’s set of counterparties could go
directly to the SPV Operator for the successful execution of a primary redemption, provided that all core principles of
the Adopted Guidance remain intact.
During an Amicable Wind Down the Minter shall continue to engage the service of a signature threshold of Validators. The
Validator(s) shall continue to check the appropriate balances of the Minter’s SPV and will publish this information to a
public forum at least once per Update Collateral Interval.
##### 4.3.2.1. Amicable Wind Down Process
The document refers to an Amicable Wind Down Period as a 90-calendar day period starting on the day after a Minter
De-Permissioning. In providing a window for the execution of an Amicable Wind Down, the Adopted Guidance intends to
avoid, as much as possible, any unfairness towards a Minter, as well as protect its ability to resolve any outstanding
liability against the protocol.
During an Amicable Wind Down, the SPV Operator shall act as the supervisor of the orderly wind down and shall assist the
Minter on a best-effort basis in redeeming its Owed $M To redeem, the Minter shall purchase and burn $M by calling the
burn() function and specifying the Minter’s address in that call, which effectively reduces the Minter’s Owed $M balance
by the burned amount. Following each Burn Event in its respective address, the Minter shall notify the SPV Operator, and
the SPV Operator shall verify the occurrence of such Burn Event onchain. Upon successful verification, the SPV Operator
shall initiate the sale of Eligible Collateral and transfer an amount to the Minter that equals the amount burned in a
Burn Event divided by the Mint Ratio, which was valid at the time the Minter De-Permissioning occurred.
During an Amicable Wind Down, the SPV Operator shall not be obliged to meet any portfolio composition requirements laid
out in the Adopted Guidance. If the Minter wishes to provide recommendations to the SPV Operator concerning the sale of
Eligible Collateral with respect to (including but not limited to) minimum execution price, execution time and type of
instrument, such recommendations shall not be binding. However, the SPV Operator shall commit to following them solely
on a best-effort basis. If a full repayment of the Minter’s Owed $M is reached within the Amicable Wind Down period, any
residual financial value shall be paid to the Minter.
If a full repayment is not reached within the Amicable Wind Down period, no further amounts shall be paid to the Minter
and the SPV Operator shall initiate a Non-Amicable Wind Down, as described below.
For sake of clarity: during the Amicable Wind Down Process, the option to transition into a Non-Amicable Wind Down
Process with immediate effect is not excluded. While it is the main goal of the SPV Operator to protect the stability of
M, this can only be achieved if the SPV Operator does not face a situation where itself might be in violation of
applicable laws within the jurisdiction it is operating from. Therefore, in the case of severe changes in circumstances,
especially but not limited to its contractual relationship with the Minter, leading to the situation that the SPV
Operator needs to cease its contractual relationship with the Minter to maintain compliance, the SPV Operator might
transition from a Amicable to a Non-Amicable Wind Down Process with immediate effect in regards to the Minter.
##### 4.3.2.2. Non-Amicable Wind Down Process
Once the Amicable Wind Down Period has lapsed, and provided that the Owed $M exceeds 0 and the Collateral Storage still
contains collateral, the SPV Operator shall unilaterally and contractually wind down the Minter’s structure through the
so-called Non-Amicable Wind Down. In case the Minter has not been de-permissioned before, i.e. if the Non-Amicable Wind
Down has been triggered by exceptional conditions described below, the SPV-Operator is required to submit a governance
proposal to de-permission the respective Minter. The SPV Operator shall act as a selling agent of the collateral and
shall distribute all proceeds in accordance with the rules of the Adopted Guidance. More specifically, as part of the
Non-Amicable Wind Down, the SPV Operator shall no longer disburse any proceeds from the sale of collateral to the
Minter.
As part of a Non-Amicable Wind Down, in the case a Minter is incapable or non-cooperative of redeeming, a Minter’s set
of counterparties could go directly to the SPV Operator for the successful execution of a primary redemption, provided
that all core principles of the Adopted Guidance remain intact and that the appropriate contractual obligations between
Minter and counterparties regulate those situations.
The SPV Operator shall exercise best effort in reaching the maximum reduction of Owed $M related to the Minter part of
the Non-Amicable Wind Down process by, e.g.:
* Waiting for collateral maturity while interrupting the automatic replenishment (run down the portfolio) if the market
conditions require to do so, and subsequently burn such $M on the Minter’s behalf.
* Selling all remaining collateral on a best effort basis and using all available proceeds to purchase $M on the open
market, and subsequently burn such $M on the Minter’s behalf.
* Enter into contractual agreements to dispose of assets and liabilities to another Minter in the network still in the
Normal Course of Business.
Should the SPV Operator, due to market conditions surrounding the purchases, have remaining $M balance in its control,
although the Owed $M of the Minter is 0, the SPV Operator shall burn such $M and increase the system-wide implicit
overcollateralization. Should purchased $M not be sufficient to fully bring the Owed $M of the Minter to 0, such Owed $M
will continue to exist, effectively decreasing the Protocol-wide overcollateralization. Governance should act
proactively to ensure that such a scenario will never materialize, by appropriately monitoring individual
collateralization levels and/or de-permissioning Minters that constitute excessive risk for the Protocol.
In addition to Minter De-Permissioning, the Adopted Guidance considers additional circumstances that should lead
immediately to a Non-Amicable Wind Down. Those circumstances are described below.
##### 4.3.2.3. Minter Insolvency
The SPV Operator shall enter into a Non-Amicable Wind Down immediately as soon as they receive a valid notification that
a Minter has filed for (or has been filed for) insolvency.
The Minter shall need to indemnify the SPV Operator for any claims brought forward by an insolvency administrator
against the SPV Operator in context of a insolvency-related Non-Amicable Wind Down.
##### 4.3.2.4. Unauthorized Termination of Minter – SPV Operator Agreement
It is crucial that all Eligible Collateral is always available to back the Owed $M To that end, it is mandatory that
Minters contract with SPV Operators who are permissioned by Governance and listed in the Adopted Guidance to operate the
Collateral Storage.
To avoid any circumvention of Governance-led rules, e.g. by exchanging the SPV Operator with an unauthorized party, it
is expected that Minters shall only replace an SPV Operator with another permissioned SPV Operator.
In the case a Minter, while remaining a Permissioned Minter or within the Amicable Wind Down Period, terminates the
Minter-SPV Operator Agreement without replacing it with another viable agreement meeting the criteria as outlined in the
Adopted Guidance, the SPV Operator shall perform an immediate Non-Amicable Wind Down. A termination during a
Non-Amicable Wind Down shall generally be excluded.
#### 4.3.3. Operational Obligations of SPV Operators
To ensure smooth operations the SPV Operator needs to comply with the following additional obligations.
##### 4.3.3.1. Co-signature of the SPV for significant payments
To protect against unauthorized transactions that could lead to significant loss of funds, the SPV Operator shall set up
the respective Collateral Storage such that the signature of the SPV (via its corporate service provider) shall be
required (in addition to its own) in order to externally transfer financial resources that are considered significant
payments.
The definition of what constitutes significant payments, as well as the respective sign off limits, shall be assessed
and if necessary, adjusted on a quarterly basis such that payments exceeding the typical interest payments (in case
existing) require the signature of the SPV via its corporate service provider.
##### 4.3.3.2. Cooperation with Validators
The SPV Operator shall cooperate in any required way, as defined in the Adopted Guidance, with Validators in the
network. This includes supporting the Minter to provide read access to the Validator of the Collateral Storage.
##### 4.3.3.3. Maintenance of Administrative Buffer
**Administrative Buffer** is defined as a reserve of 25,000 worth of Deposit Equivalents that the SPV Operator can use
to cover possible offchain administrative costs, such as assisting with the Wind Down of the Minter or resolving legal
disputes, according to the rules set out in the Adopted Guidance. Such a buffer should exist within the Collateral
Storage in a way considered satisfactory by the SPV Operator, but should not in any case be simultaneously pledged to
the system as Eligible Collateral for minting. For the avoidance of doubt, the SPV Operator shall be allowed to use the
Administrative Buffer to ensure operations in cases where the Minter is unwilling or unable to cooperate. Where the
Administrative Buffer is not meeting the minimum amount specified in the Adopted Guidance, the Minter shall not submit
any mint proposals. Should the Minter still submit a mint proposal despite an insufficient Administrative Buffer, the
SPV Operator shall socialize this concern (i.e. through the network of Validators, Governors, or other stakeholders). As
in any social consensus construct, stakeholders are expected to act accordingly, in their own interest. Only following
the completion of a Wind Down, any unused part of the Administrative Buffer is to be paid back to the Minter.
##### 4.3.3.4. No Wire Back Instructions
The SPV Operator shall not wire back any financial resources to the Minter except for:
* Orderly Retrieval Process.
* Residual financial value, if applicable following a Wind Down process described above.
### 4.4. Guidelines for Submission of Approval Requests
An application for the approval as SPV Operator shall be submitted via a change proposal of the Adopted Guidance
following the process described in
[Change Process for the Adopted Guidance](/home/fundamentals/adopted-guidance/description/#change-process-for-the-adopted-guidance).
The change proposal shall aim to add the SPV Operator to
[Contact Information of Currently Approved SPV Operators](/home/fundamentals/adopted-guidance/spv-operators/#contact-information-of-currently-approved-spv-operators).
Before submitting its application, the SPV Operator shall publish a KYC report via appropriate channels, as well as
appropriate proof that the requirements set forth in
[Obligations of SPV Operators](/home/fundamentals/adopted-guidance/spv-operators/#obligations-of-spv-operators) are met:
* Certified copies of the commercial register and/or trade register and/or register of companies or alike, proving that
the company was duly incorporated in the jurisdiction it is providing its services from.
* Proof of who the ultimate beneficial owners (UBO) of the company are, including proof for negative politically exposed
persons (PEP) / sanctions check.
* Certified copies of all required official licenses required to operate the business and to provide the services
outlined in the Adopted Guidance and in the contractual agreements where the SPV Operator is a party to.
* A legally binding declaration that no insolvency, bankruptcy or similar/comparable proceedings are currently pending
or to be anticipated in the foreseeable future in relation to the company.
* Proof that the company is minimally affiliated (via its shareholders) with or (practically) controlled by any
Validator and/or Minter named as permissioned actor in the Adopted Guidance. When there is some level of affiliation,
relevant evidence of appropriate corporate governance shall be presented.
* A legally binding declaration, as long as objectively and legally possible, to set up its business activities in
regards to the services and contractual relationships as outlined in the Adopted Guidance.
* A legally binding commitment, as long as objectively and legally possible, to amend its contractual relationships
reflecting potential mandatory changes of the Adopted Guidance.
Where confidentiality concerns emerge, the M0 Foundation can step under Non-Disclosure Agreements to analyze the
required documentation and provide a qualified public opinion to the ecosystem.
## Adopted Guidance PDF Version
v1.50 in PDF format.
Find below the version 1.50 of M0's Adopted Guidance, a document that outlines the core rules of engagement outside of the enforceable domain of the protocol smart contracts for the various actors in the M0 ecosystem.
> [https://github.com/m0-foundation/adopted-guidance/blob/main/m0\_adopted\_guidance\_v1.50.pdf](https://github.com/m0-foundation/adopted-guidance/blob/main/m0_adopted_guidance_v1.50.pdf)
## 3. Eligible Collateral
### 3.1. Criteria for Eligible Collateral
We expect Validators to solely and exclusively recognize assets as Eligible Collateral when such assets conform to the
criteria below.
#### 3.1.1. Criteria for the Eligibility of Assets
i. Funds (denominated in the Reference Currency) credited to an account at a United States Federal Reserve Bank.
ii. Funds (denominated in the Reference Currency) held as demand deposits or insured shares at an insured depository institution - subject to specific institution in scope.
a. Such financial institutions shall be deemed to be automatically in scope if the totality of deposits are held in deposit placement networks. (Note: Minter must perform a credit suitability assessment prior to placing funds at such an institution.)
b. List of institutions additionally deemed in scope: (1) The Bank of New York Mellon Corporation; (2) DekaBank Deutsche Girozentrale; (3) Lead Bank.
iii. United States Treasury securities with a remaining time to maturity of 180 days or less.
iv. Funds (denominated in the Reference Currency) received under repurchase agreements where the Minter is the seller and with an overnight maturity.
v. Reverse repurchase agreements where the Minter is the cash lender and with an overnight maturity, that are backed by United States Treasury securities and other government securities such as securities issued or guaranteed by government agencies, including, among others, Fannie Mae, Freddie Mac, and the Federal Home Loan Bank. In the event that collateral is not in the form of United States Treasury securities, such reverse repurchase agreement must be overcollateralized at a rate deemed to be appropriate by the Minter.
vi. Securities issued by a regulated investment company, regulated fund or supervised entity, that is subject to prudential supervision in its home jurisdiction, provided that such vehicle invests exclusively in assets described in items (i), (ii), (iv) and (v). Specific to the requirements in (iii), securities shall be permitted to have a remaining maturity of 397 days or less so long as the weighted average portfolio maturity is 60 days or less and the weighted average portfolio life to maturity is 120 days or less.
vii. Any version of items (i), (ii), (iii), (vi) in tokenized form, provided that such versions comply with all applicable laws and regulations - subject to specific definition of the financial product in scope.
a. List of approved products: (1) Superstate Short Duration US Government Securities Fund (commonly referred to as USTB); (2) BlackRock USD Institutional Digital Liquidity Fund (commonly referred to as BUIDL).
#### 3.1.2. Ancillary Criteria for the Eligibility of Assets
* So-called In-Transit Cash, defined as Deposit Equivalents in an amount equal to placed-and-executed-but-not-yet-settled buy orders for assets defined in 3.1.1. In case such orders are ultimately canceled, settled, or never settled, such balances shall no longer be recognized.
* So-called In-Transit Securities, defined as executed-but-not-yet-settled sell orders for assets defined in 3.1.1 (at the time of purchase) in case, for the avoidance of doubt, neither the securities nor balances in Deposit Equivalents are listed in the Collateral Storage (and more specifically Custody Account and Deposit Account of the SPV).
### 3.2. Valuation Policy for Eligible Collateral
Eligible Collateral shall be recognized at their daily market value (according to the last closing price) published on [www.treasurydirect.gov](http://www.treasurydirect.gov). Alternatively, for approved wrappers, the recognition of the most recent NAV (net asset value) calculated by a third-party agent and published by the asset manager / fund administrator is admissible.
It is understood that Validators and Minters might not observe the same market price in case they access the market data at different times. Minters shall consider this when requesting signatures for updateCollateral() calls from Validators. With Signature we refer to the cryptographically hashed meta information of a transaction with the private key of the Validator which allows the Minter to perform a certain Protocol transaction after the Validator has verified that the conditions for such a transaction are met.
* So-called In-Transit Cash, defined as Deposit Equivalents in an amount equal to
placed-and-executed-but-not-yet-settled buy orders for assets defined in (i). In case such orders are ultimately
canceled, settled, or never settled, such balances shall no longer be recognized.
* So-called In-Transit Securities, defined as executed-but-not-yet-settled sell orders for assets defined in (i) (at the
time of purchase) in case, for the avoidance of doubt, neither the securities nor balances in Deposit Equivalents are
listed in the Collateral Storage (and more specifically Custody Account and Deposit Account of the SPV).
### 3.2. Valuation Policy for Eligible Collateral
Eligible Collateral shall be recognized at their daily market value (according to the last closing price) published on
[www.treasurydirect.gov](http://www.treasurydirect.gov). Alternatively, for approved wrappers, the recognition of the most recent NAV (net asset value)
calculated by a third-party agent and published by the asset manager / fund administrator is admissible.
It is understood that Validators and Minters might not observe the same market price in case they access the market data
at different times. Minters shall consider this when requesting signatures for updateCollateral() calls from Validators.
With Signature we refer to the cryptographically hashed meta information of a transaction with the private key of the
Validator which allows the Minter to perform a certain Protocol transaction after the Validator has verified that the
conditions for such a transaction are met.
In proposing valuation options for Eligible Collateral, the Adopted Guidance document has considered two alternative
options: mark-to-market, and at-cost, opting ultimately for a mark-to-market approach. The two options somehow reflect
similar accounting methodology for so-called held for trading or available for sale assets, and have a set of pros and
cons that have been analyzed when proposing one over another:
**Fair value representation**: the current executable market price of an asset remains the most accurate representation
of its value at any given time. By opting for a mark-to-market approach Minters would see the immediate benefit of the
appreciation of a fixed income asset as it approaches maturity, without having to realize it. Given the eligible
instruments, we believe that a mark-to-market approach would allow Minters to operate based on the most accurate level
of overcollateralization.
**Non-arbitrage**: reflecting collateral value fluctuations onchain can prevent misalignments and unexpected behaviors
due to arbitrage opportunities among parties. This is particularly important in scenarios of extreme price movements
driven by significant interest rate changes. We are aware that recognizing the mark-to-market fluctuations of fixed
income instruments onchain, even without the obligation for a Minter to redeem those assets at will, could expose the
structure to interest rate risk and, in extreme scenarios, bank-run phenomena. While we believe that those effects
should be accounted for and mitigated by appropriate levels of overcollateralization, similar to what happens for a
bank's core capital, we think that the nature of Eligible Collateral today, as well as the dominant macroeconomic
conditions, make those risks manageable.
**Capital efficiency**: the continuous increases in Minters' Owed $M due to the accrual of Minter Rate introduces
natural pressure on the Collateralization Ratio. Valuing collateral at market price can alleviate this pressure, due to
the time value of the instruments currently considered eligible.
It is important to remember that the recommended Valuation Policy is strictly interdependent with the nature of the
Eligible Collateral. Following an evolution of the eligibility criteria, e.g. expanding towards longer maturities of
different asset classes, will require, among other things, a revision of the Valuation Policy. This will be valid also
in case of significant shifts in the current economic landscape.
Collateral that is to be considered Eligible Collateral under the conditions set forth in Ancillary Criteria for the
Eligibility of Assets shall be valued without a haircut.
### 3.3. Collateral Storage
Collateral shall only be recognized as Eligible Collateral by the Validator if this collateral is appropriately held in
the Collateral Storage.
Given the current technological conditions, and the nature of the Eligible Collateral proposed by the Adopted Guidance,
with appropriate Collateral Storage we refer specifically to the fact that the entity that legally owns the collateral
(SPV) fulfills a set of requirements as described below. The fulfillment of these requirements should be verified
through cooperation between Minters (via appropriate transparency), Validators, and Governance oversight in a way deemed
satisfactory by Governance. The appropriate execution of the Mandatory Contracts described above should satisfy those
conditions.
Based on this set of fundamental requirements, the SPV:
* is an orphaned entity;
* is minimally affiliated (via its shareholders) with or (practically) controlled by any Validator or Minter or the
affiliates of any Validator or Minter;
* does not conduct any other business except the storage of collateral for Minters and issuance of Notes to Minters;
* is a restricted-purpose vehicle that prevents entering into any other liabilities but the ones directly related to the
storage of collateral and the issuance of Notes against such collateral;
* is designed to be insolvency remote;
* is incorporated in an Approved Jurisdiction, as described below;
* possesses all required licenses and permissions (if necessary) to store collateral and to issue Notes against such
collateral;
* is audited on an annual basis by a licensed auditor deemed appropriate to perform the task.
Additionally, any Notes issued to the Minter shall comply with the following in order to be considered Eligible
Collateral:
* Full asset segregation of collateral not only at the Minter level, but also at the level of each wallet address
controlled by the Minter — in case the Minter controls more than one permissioned address.
* Limited recourse so that the claims of any Minter are strictly limited to the collateral belonging to its Notes.
### 3.4. Approved Jurisdictions
Given the current technological conditions, and the nature of the Eligible Collateral proposed by the Adopted Guidance,
any form of collateral shall only be recognized as Eligible Collateral by the Validator if the Collateral Storage is
located in one of the jurisdictions listed below:
* Luxembourg
* The Cayman Islands
* United States of America
As for other elements of the Adopted Guidance, the list of Approved Jurisdictions (as described above) can be amended
any time through a Governance vote. In case the list is amended, Actors are expected to ensure compliance with and
enforceability of the rules set out in the Adopted Guidance in such a new jurisdiction.
Candidates for Approved Jurisdictions shall comply with the following minimum requirements:
#### 3.4.1. Bankruptcy Remoteness
The jurisdiction provides legal and structural mechanisms that reduce the risk of insolvency for the legal entity,
reinforcing the effectiveness of provisions that prevent creditors and investors from initiating insolvency proceedings
against the legal entity or seizing its assets or the assets of its compartments, estates or sub-funds.
#### 3.4.2. Non-Petition and Non-Seizure Provisions
Jurisdictions must uphold provisions that prevent creditors and investors from initiating insolvency proceedings against
a legal entity or seizing its assets or the assets of its compartments or estates or sub-funds, thus ensuring the legal
entity's bankruptcy remoteness.
#### 3.4.3. Priority of Payments and Subordination
The legal system recognises and enforces contractual or statutory arrangements that clearly define the priority of
payments and the subordination of claims among investors and creditors.
#### 3.4.4. Regulatory Compliance
The jurisdiction has a regulatory framework that supports the business activities of the legal entity, including clear
guidelines for the trading of financial instruments and Eligible Collateral associated with them.
#### 3.4.5. Legal and Regulatory Framework
The jurisdiction has a legal and regulatory framework that permits the issuance of financial instruments and the
investment in Eligible Collateral.
#### 3.4.6. Political Stability
The jurisdiction exhibits a high degree of political stability, characterized by a consistent and predictable legal and
regulatory environment, minimal political unrest, and an upholding of the rule of law.
#### 3.4.7. Dispute Resolution
An effective legal framework for dispute resolution is in place, including access to courts or arbitration panels.
#### 3.4.8. Asset Segregation
Collateral for each Minter is either to be stored in a separate legal entity per Minter or the jurisdiction must provide
legal frameworks that enable the establishment of separate compartments or estates or sub-funds within a single legal
entity, ensuring that each compartment or estate or sub-fund is its own distinct pool of assets and liabilities. There
must be clear legal recognition that assets within one compartment or estate or sub-fund are exclusively reserved for
the investors and creditors of that compartment or estate or sub-fund and are protected from the claims of creditors of
other compartments or estates or sub-funds and claims of creditors of the legal entity to ensure the functioning of the
intended wind down processes, as described below.
#### 3.4.9. Limited Recourse
The legal system must enforce that the claims of investors and creditors are strictly limited to the assets of the
compartment or estate or sub-fund to which they have exposure. In cases where the assets of a compartment or estate or
sub-fund are insufficient to satisfy the claims, the legal framework permits the extinguishment of any remaining claims,
prohibiting further recovery efforts.
#### 3.4.10. Investor Protection
There are robust mechanisms in place to protect investors, including clear disclosure requirements and the enforcement
of fiduciary duties by the managers of the legal entity.
#### 3.4.11. Operational Infrastructure
The jurisdiction boasts a developed financial infrastructure capable of supporting complex financial transactions,
including experienced service providers and legal counsel with expertise in financial matters.
#### 3.4.12. Custody Relationship
The jurisdiction recognises and enforces the legal nature of the custody relationship between the legal entity and its
custodian, ensuring that assets held in custody are protected and segregated from the custodian's general estate in the
event of its bankruptcy.
#### 3.4.13. Audit
The jurisdiction requires the mandatory (by law) audit of the annual financial statements of the legal entity.
Where such audits are not required by law in the jurisdiction, the audit obligation must be embedded contractually in
the entity’s governing documents or operator agreements.
import ZoomableImage from '@/components/ZoomableImage';
## 2. Ecosystem Description
The M0 Protocol is designed to support permissioned Actors in the minting of a crypto asset called $M The minting of $M
requires collaboration across a set of Actors complying with a set of rules, some of which can be enforced onchain.
First and foremost, a sufficient balance of Eligible Collateral (as described in the Adopted Guidance) must be
constantly identifiable and constrained for the backing of Owed $M This requires a demonstration of the existence and
validity of the collateral, repeated on an ongoing basis, and executed via the updateCollateral() function call. Only if
sufficient collateral balance has been updated and only if such balance is still valid within the timeframe implied by
the collateral update interval can a Minter (see below) mint $M or retrieve collateral.
With Eligible Collateral we refer to the types of collateral that are defined to be suitable to back M, based on the
criteria listed in
[Criteria for Eligible Collateral](/home/fundamentals/adopted-guidance/eligible-collateral/#31-criteria-for-eligible-collateral).
With Collateral Balance we refer to the onchain value of the Eligible Collateral available to mint $M
The sufficiency of the Collateral Balance is determined by the following Core Operating Condition:
```math
(Collateral \ Balance - Total \ Pending \ Retrievals) \times Mint \ Ratio
\geq
Owed \ $M + Proposed \ M
```
Subject to the following definitions:
* **Total Pending Retrievals** indicate the total amount of collateral a Minter is trying to retrieve from SPV by
submitting a Retrieval Request to the protocol via the proposeRetrieval() function.
* **Retrieval Request** is the process by which a Minter submits a request to the Protocol to retrieve a specified
amount of $M The Protocol verifies that the Core Operating Condition would not be breached by the retrieval of this
amount of M, based on the latest Collateral Balance.
* **Mint Ratio** refers to the fraction of a Minter’s Collateral Balance that can be used to generate M, which
effectively controls the leverage of a Minter and the over-collateralization of $M
* **Owed M** describes the amount of $M generated by the Minter, plus the Minter's Accumulated Minter Rate and Penalty,
still outstanding — i.e. not burned.
* **Proposed M** is the amount of new $M that a Minter is requesting to mint.
The Core Operating Condition intends to guarantee that no $M can be minted and/or no collateral can be retrieved unless
there is sufficient Collateral Balance.
We define Burn (or Burned M) as a successful call to the `burn()` function, which specifies a Minter’s address. Such
operation effectively reduces that Minter’s Owed $M balance by the amount of burned $M
### 2.1. Actors
Actors are relevant participants (roles and entities) within the M0 ecosystem. Only a subset of the Actors (so-called
Permissioned Actors) are explicitly approved through Governance, while the rest is explicitly, albeit more loosely,
identified as part of the Adopted Guidance.
The following diagram displays a schematic view of the Actors within the M0 ecosystem and how they connect to external
parties (i.e. parties whose practices are not covered by the Adopted Guidance) and contracts that need to be in place
between Actors. The list of Actors is defined below.
**Minter**: The Minter is an entity considered orphaned from the risk of exogenous insolvency, operated by a separate
entity responsible for business operations that is in place to further distance the Minter from bankruptcy issues
(referred to here as the BD Minter). The Minter owns the private key associated with a public address that is
permissioned by Governance to interact with a minting smart contract in order to mint $M against a sufficient Collateral
Balance, represented by Notes which are issued by the SPV (see below for details) and held on the Minter’s balance
sheet.
The Minter is not allowed to conduct any other business or take on any other liabilities except the $M balance it owes
to the Protocol and the contractually defined liabilities vis-à-vis the BD Minter.
**BD Minter**: The BD Minter is the business development entity that performs the operational obligations of the Minter
and contractually absorbs any and all potential liabilities arising from agreements with service providers of the
Minter. While the suggested exclusive relationship between the Minter and the BD Minter should not be overly
prescriptive, we emphasize that this provision aims to maintain liability segregation, keeping all non-minting
responsibilities away from the Minter.
**SPV Operator**: The SPV Operator manages the portfolio of collateral in the SPV on behalf of the SPV but for the
benefit of a Minter’s business operations. The SPV Operator also acts as selling agent in the context of a wind down (as
described in
[Obligations Outside of the Normal Course of Business](/home/fundamentals/adopted-guidance/spv-operators/#obligations-outside-of-the-normal-course-of-business))
and can even wind down a Minter in cases where the Minter is unable or unwilling to comply with the Protocol rules.
**SPV**: The SPV is the orphaned and insolvency-remote legal owner of the Eligible Collateral, available financial
resources, or of any other asset which is managed by the SPV Operator.
**Validator**: The Validator independently verifies that the amount of collateral to be published onchain appropriately
exists and is compliant with appropriate eligibility criteria. We expect the role of Validator to evolve jointly with
the evolution of the nature of the underlying available collateral (e.g. appropriate tokenization).
### 2.2. Mandatory Contracts
**Mandatory Contracts** are contracts that are required to be in place between the Actors to ensure maximum protection
for the collateral as well as maximum degree of enforceability of the Protocol rules. The execution of these contracts
should improve robustness against collusion among malicious Actors.
As with many aspects outlined in the Adopted Guidance, the following set of contracts is intended for scenarios where no
robust onchain equivalent exists. With advancements in tokenization efforts for instruments deemed Eligible Collateral,
we anticipate that some of these contracts may become redundant in future updates of the Adopted Guidance.
Given that Governance participants and holders of $M are likely not contractual parties to any of the Actors, the
Mandatory Contracts are designed to ensure alignment among all Actors involved in the legal and operational framework,
ensuring compliance with Protocol rules.
**Minter Operating Memorandum**: The Minter is an entity with a contractually-limited purpose to hold Notes and mint $M
Therefore, it is not allowed to e.g. hire personnel and build operational resources or directly enter into distribution
agreements, as well as sale/ purchase agreement, with buyers of $M Notes are defined as pass-through and look-through
limited recourse notes issued by the SPV in one or more tranches in accordance with the Terms and Conditions of the Note
itself.
The Minter Operating Memorandum should regulate that:
* The BD Minter provides sufficient operational resources to the Minter for its operations, given that the Minter should
not have operational staff.
* The BD Minter and Minter should have appropriate liability segregation in order to limit eventual spillovers between
general liabilities and those solely related to the Protocol with regards to the requirement to back the Owed $M by
sufficient Collateral Balance.
Minter-SPV Operator Agreement: The relationship between the Minter and the SPV Operator is of core importance since the
intentional split between an entity that mints $M and another entity that manages the collateral creates a field of
tension between collaboration, control and potential enforcement. The Minter-SPV Operator Agreement should regulate this
relationship. It differentiates between two scenarios:
* a) Normal Course of Business
* b) Outside Normal Course of Business
While the Normal Course of Business regulates the obligations of the SPV Operator to maintain the Eligible Collateral
balance, the Outside Normal Course of Business ensures that the SPV Operator is allowed to perform actions against the
Minter to enforce the Protocol rules in case the Minter is not compliant.
This agreement is crucial for the protection of the collateral.
**Minter-Validator Agreement (where required)**: The Validator requires broad ongoing visibility of the collateral
storage.
With **Collateral Storage** we identify the collection of venues such as Securities and Deposit Accounts, as well as
digital asset accounts, held by the SPV.
The services provided by a Validator to the Minter (and, indirectly, the ecosystem) need to be well-defined to ensure
the smooth operation of the Protocol. Therefore, this agreement, where required, should regulate all transparency,
access and confidentiality aspects between the parties, as well as the service of validation in compliance with the
Protocol rules.
**Terms and Conditions (and Subscription Agreement) of the Note**: The Terms and Conditions of the Note (as well as
Subscription Agreement) represent the economic link between the collateral and the Minter entity. Their enforceability
and protective clauses have an obvious direct impact on the quality and availability of the collateral for the purpose
of the Protocol. Additionally, these documents need to reflect the scenarios where the SPV Operator winds down a Minter
balance without the Minter’s collaboration. This requires certain rights of a party (the SPV Operator) which is not the
noteholder (Minter) that need to be mandatorily reflected in the Terms and Conditions of the Note itself.
**SPV Operating Memorandum**: Every Actor should practically be replaceable; in particular any operation of the SPV
should be highly standardized. This is what an appropriate SPV Operating Memorandum should ensure. As a crucial point
this document also regulates the transfer restrictions of funds transferred out of the Collateral Storage.
In case any of the Mandatory Contracts defined here are modified as part of the change process, all parties shall work
to amend the respective contracts as soon as practically feasible.
Parties are not expected to perform changes on their Mandatory Contracts (or similar) that are not made in the spirit of
the Adopted Guidance. Parties are expected to remedy such misaligned clauses by replacing them as soon as practical and
without undue delay.
### 2.3. Duty of Transparency
To the extent reasonable, and observing proper confidentiality practices, all Actors within the M0 ecosystem are
encouraged to abide by a duty of transparency and candor toward Governance. The Actors will enter into Mandatory
Contracts with each other and are expected to timely and appropriately make the latest versions of such agreements
available to Governance and other interested parties, ideally in the public domain, but certainly upon request.
### 2.4. Core Operational Flows
While the Protocol is immutable and the direct impact of Governance on the Protocol’s behavior is limited to the
[Governance Controlled Parameters](/home/fundamentals/whitepaper/protocol/#governance-controlled-protocol-parameters)
(the set of variables that can be modified by Governance votes and that have an impact on the Protocol's functioning)
and the permissioning of certain Actors, calls of Protocol functions should go hand in hand with offchain processes that
the Actors must comply with. The following processes are, at the current state of technology and collateral eligibility,
to be considered binding for all Actors.
#### 2.4.1. Update Collateral Process
The updateCollateral() function (Update Collateral) needs to be called once per Update Collateral Interval.
**Update Collateral Interval**: In accordance with Protocol specifications, the period between which Update Collateral
must be called by a Minter. If Minters do not call Update Collateral within this amount of time after their previous
call, their onchain Collateral Value is assumed to be 0 and they will incur a penalty rate on the next update. This
protocol parameter is alterable with a Standard Proposal.
The **Penalty Rate** is defined as the percentage charged on Owed $M that is in excess of the amount a Minter is
permitted to have generated. It is assessed any time Impose Penalty is called, which is embedded in both Update
Collateral and Burn. It is alterable with a Standard Proposal. This is a fixed percentage and not an annualized rate.
Failure to do so will result in the Collateral Balance being set to 0 and the application of a respective Penalty.
Some key financial definitions are outlined below:
* **Deposit Equivalents** are amounts, denominated in the Reference Currency, held in either Deposit Accounts or in the
form of so-called stablecoins (including M) in wallets.
* **Deposit Account** is the demand deposit account held by the SPV with demand deposit providers.
* **Custody Account** is the securities account or digital asset account held by the SPV with custody providers.
* **The Reference Currency** throughout this document is USD (United States Dollar).
An example collateral update flow, assuming a look-through validation of the collateral, is shown in the picture below:
1. The Minter purchases Notes from the SPV with Deposit Equivalents.
2. The SPV Operator on behalf of the SPV will perform the collateralization process.
3. Minter sends a Validation Request (see below) which contains the amount it would like to update onchain to the
Validator.
4. Validator verifies the existence of the Collateral (e.g. via its read access to the depository account or, in case of
eligible tokenized collateral, via observation of distributed ledger entries), verifies the compliance with the
eligibility criteria and, potentially, verifies the implementation of Mandatory Contracts.
5. If all checks have been successful, the Validator sends a Signature to the Minter.
6. With this Signature the Minter can call the updateCollateral() function.
**Validation Request**: A call from the Minter to the Validator where the Minter requests a Signature for either
updating its Collateral Balance or for removing a RetrieveID from the Protocol.
Steps 3-6 have to be completed at least once per Update Collateral Interval. Step 2 has to be completed whenever new
funds are transferred to the Collateral Storage or whenever collateral matures thus liquidates.
#### 2.4.2. Minting M
The mint process of $M can be triggered by the Minter at any time as long as the Collateral Balance is sufficient and
the Core Operating Condition satisfied.
1. The Minter can call the proposeMint() function at any time, indicating the amount it wants to mint. The Protocol will
support the verification of the Core Operating Condition following a successful mint. The transaction will fail if
the condition is not satisfied. As a reminder, during the Mint Delay time any Validator can cancel the mint.
2. Once the Mint Delay has elapsed, the Minter can execute the mint and the Protocol will again check the Core Operating
Condition. If the mint is successful, the minted $M will be transferred to the wallet indicated in the mint request.
#### 2.4.3. Retrieval of Collateral
As defined in the [Whitepaper](/home/fundamentals/whitepaper/), and as a reminder for the reader, collateral can
generally only be retrieved (at least during the
[Normal Course of Business](/home/fundamentals/adopted-guidance/spv-operators/#obligations-in-the-normal-course-of-business)
— see below) if the Core Operating Condition remains satisfied following a successful retrieval. Given that the
retrieval flow is the main way in which funds can leave the Collateral Storage, the process should absolutely come with
strict rules. We are expecting Governance to holistically assess that those rules remain satisfied by any alternative
implementation proposed by a prospective Minter. An example of such a flow is described in the chart below:
1. The Minter requests a retrieval by calling the proposeRetrieval() function with the amount it wishes to retrieve.
2. If the Core Operating Condition remains satisfied after the retrieval the call succeeds and a RetrievalID is
generated and stored in the Protocol. For as long as the RetrievalID is not closed the Protocol will subtract the
requested amount from the Collateral Balance.
3. The Minter can now request the retrieval with the SPV Operator.
4. The SPV Operator will now validate the existence of the Retrieval Request in the Protocol.
5. If the existence of a corresponding Retrieval Request has been confirmed, the SPV Operator can initiate the
liquidation of the equivalent collateral and transfer the funds back to the Minter.
6. Once the transfer has been executed the Minter can request a Signature from the Validator for the removal of the
RetrievalID.
7. The Validator confirms via appropriately provided access to the Collateral Storage that the transfer was executed.
8. If the check was successful, the Validator provides a Signature for the removal of the RetrievalID.
9. The Minter can now call the updateCollateral() function and pass on the RetrievalID it wishes to remove together with
the Signature. The RetrievalID will be removed, and the respective amount will no longer be subtracted from the
Minter’s Collateral Balance.
### 2.5. High-Risk Jurisdictions
For the benefit of this document, the countries in the following lists are considered to be high-risk jurisdictions.
* **FATF “Black and Gray” lists.** As available, for instance, on
[https://www.fatf-gafi.org/en/countries/black-and-grey-lists.html](https://www.fatf-gafi.org/en/countries/black-and-grey-lists.html) .
* **European Commission’s list of High-risk Third Countries.** As available, for instance, on
[https://finance.ec.europa.eu/financial-crime/anti-money-laundering-and-countering-financing-terrorism-international-level\_en#strategic-deficiencies](https://finance.ec.europa.eu/financial-crime/anti-money-laundering-and-countering-financing-terrorism-international-level_en#strategic-deficiencies).
* **Any jurisdiction comprehensively subject to Office of Foreign Assets Control (OFAC) sanctions.**
* **Any jurisdiction comprehensively subject to European Union (EU) sanctions.**
import ZoomableImage from '@/components/ZoomableImage';
## 1. Description of the Adopted Guidance
Description of the Adopted Guidance's purpose and its change control process.
### 1.1. Purpose of the Adopted Guidance
The Adopted Guidance is a document produced and proposed by the M0 Foundation, and approved through the M0 Two Token
Governor (or, more simply, Governance) that outlines the core rules of engagement that exist outside of the enforceable
domain of the protocol smart contracts for the various actors in the M0 ecosystem. In this document, Protocol refers to
the deployment of the M0 Protocol on the Ethereum blockchain under the protocol address which defines the rules upon
which $M can be minted.
The **Protocol Address** is as follows: **0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b**
While Protocol governors cannot be forced to take action for any breach of these rules, it is implicitly expected that
holders of the appropriate Governance tokens will act in accordance with this document, or else change it.
The Adopted Guidance describes certain actors, characteristics (e.g. Approved Jurisdictions for Collateral Storage), as
well as business practices, binding contracts and business behavior. It also indicates, among other things, the type and
composition of collateral that is eligible to back Owed $M
Ultimately, the Adopted Guidance exists as the guiding manifesto for core business practices within the M0 ecosystem.
The expectation is that as the ecosystem continues to grow and evolve, so will the Adopted Guidance through appropriate
Governance oversight and approval. As such, this should be regarded as a living document that will continuously mature
alongside the development of the M0 project at large.
### 1.2. Change Process for the Adopted Guidance
The nature of the Adopted Guidance is such that it must allow for only one valid version per voting epoch to be agreed
upon.
In most cases, it is expected that a voting epoch will contain non-conflicting, **Discrete Change Proposals** which, if
approved by governance vote, can be immediately put into effect. In such cases, it is expected that, upon executing the
proposal, the new version of the Adopted Guidance (as specified by its document hash) is ratified and should be adopted
by ecosystem Actors. While the physical act of ratifying an approved change proposal can be done by anyone, it is
expected that the proponents, or the M0 Foundation as protector of the ecosystem, will perform such duties.
At scale, it can be expected that a voting epoch might contain conflicting Change Proposals. This poses a specific
challenge in the case where multiple changes to the Adopted Guidance are being voted on simultaneously within a single
epoch, especially since Governance requires the ability to accept or reject each change separately.
As such, when conflicting change proposals emerge, the process of amending and updating the Adopted Guidance should be
split into two votes:
* 1. In the first voting epoch, one or multiple proposals are voted on. They can be accepted or rejected independently on an individual basis (i.e. the Discrete Change Proposal).
* 2. During the second voting epoch, all individually approved proposals are consolidated into a new version of the Adopted Guidance via voting (i.e. the Executive Change Proposal). While the physical act of consolidation and an Executive Change Proposal can be done by anyone, we are expecting some of the proponents, or the M0 Foundation as protector of the ecosystem, to perform such duties.
We expect proponents to clearly state whether a proposal should be considered as a Discrete Change Proposal or an
Executive Change Proposal.
New proposals shall only be deemed valid upon confirmation of the relevant Executive Change Proposal.
An example flow is detailed below:
A proposal shall clearly reference each section and/or sentence that is subject to change and clearly state what it
shall be replaced with. Any ambiguity that could lead to different interpretations for the consolidated version will be
avoided. In order to facilitate individual votes, a proposal should be formulated in a way that is as atomized as
possible.
The visual below shows an example of an appropriately proposed amendment to the Adopted Guidance:
:::note
Date: June 22, 1633
Author: Galileo Galilei
Adopted Guidance - Discrete Change Proposal
Context (option, will not become a part of the Adopted Guidance): This change is required to explain observations made
on planets.
Old: “0.0.1 The earth stands still.”
New: "0.0.1 The earth moves around the sun.stands still."
:::
In the event of conflicting versions or proposals, the most recently executed proposal that was executed onchain should
be considered valid.
## 6. BD Minters and Minters
### 6.1. Contact Information of Currently Permissioned Minters
:::note
Minter One Generator (SPV) Ltd.
171 Main Street, PO Box 92, Road Town, British Virgin Islands VG1110
[minter.one.generator@mxon.co](mailto\:minter.one.generator@mxon.co)
[www.mxon.co](http://www.mxon.co)
:::
:::note
Bridge Building, Inc.
2120 University Ave Suite 213 Berkeley, CA 94704
[www.bridge.xyz](http://www.bridge.xyz)
:::
### 6.2. Eligibility Criteria for Permissioned Minters
BD Minters and Minters shall be set up and operated in compliance with the requirements and criteria described in the
Adopted Guidance. In case the setup and operations of the BD Minter and/or Minter requires decisions on matters that are
not covered in the Adopted Guidance, the stability, safety and availability of $M shall be the sole guiding principle
for such decisions.
Per one Minter entity, the current version of the Adopted Guidance allows only one Minter Address to be permissioned.
Minters shall be set up in such a way which maximizes insolvency remoteness. They shall not be allowed to conduct any
other business but interact with the Protocol for the management of $M. This excludes, for example, in particular any
commercial and employment agreements. In principle, the Minter entity shall be fully owned or at least controlled by a
BD Minter. Alternatively, a Minter shall be orphaned and operated by the BD Minter. Permissioned Minters are expected to
fulfill the following criteria:
* Be a duly incorporated entity, not in a
[High-Risk Jurisdiction](/home/fundamentals/adopted-guidance/ecosystem/#high-risk-jurisdictions).
* Have the technical ability to interact with the Protocol.
* Be able to provide signed Minter-SPV Operator agreement with a permissioned SPV Operator.
* Have all licenses and permissions required in their jurisdiction.
* Have reasonably strong insolvency remoteness.
* Be orphaned, solely owned, or controlled by the BD Minter.
* Be affiliated to a BD Minter that is not incorporated in a
[High-Risk Jurisdiction](/home/fundamentals/adopted-guidance/ecosystem/#high-risk-jurisdictions).
* Have established practices that are consistent with internationally accepted standards (such as the Financial Action
Task Force (FATF) Recommendations) in regards to policies and procedures reasonably designed to prevent the Minter
from facilitating money laundering and terrorist financing including an established KYC program and identifying and
reporting suspicious activity, as appropriate.
* Have established policies and procedures designed to prevent the Minter through the BD Minter from processing
transactions that violate OFAC or EU sanctions, including by implementing transaction screening procedures to identify
OFAC listed Specially Designated Nationals (SDNs) and persons normally resident in jurisdictions subject to
comprehensive sanctions.
Minters should not enter into any other agreements except the following:
* Agreements necessary to purchase Notes issued by the SPV.
* Agreements with its BD Minter for the provision of services required to operate the business of the Minter.
* Funding agreements with its BD Minter for the sole purpose of financing collateral purchases.
* Minter-Validator Agreement(s) and Minter-SPV Operator Agreement(s).
* Agreements reasonably necessary for compliance with regulatory obligations including anti-money laundering and
sanctions checks (can be fulfilled by the BD Minter on behalf and for the Minter).
Every agreement that the Minter enters into shall be co-signed by the BD Minter and shall contain a clause whereby the
BD Minter absorbs any and all financial liabilities arising from such agreements.
### 6.3. Obligations of Minters
The provisioning of sufficient collateral for its Owed $M is deemed to be in the responsibility of the Minter,
irrespective whether such Owed $M was caused by the minting of M, applied Minter Rate or Penalties. It is the Minter’s
obligation to at all times provide enough capital to the SPV so that the SPV Operator can purchase sufficient Eligible
Collateral.
Minter’s need to demonstrate that they provided a sufficient amount of Eligible Collateral via calling the
updateCollateral() function at least once per every Update Collateral Interval. If they fail to do so a Penalty will be
added to the Owed $M for the time they are not in compliance with Protocol rules.
Minters need to at all times have a valid Minter-SPV Operator Agreement which meets the criteria set in place with an
SPV Operator that is and remains for the time of service listed in
[Contact Information of Currently Approved SPV Operators](/home/fundamentals/adopted-guidance/spv-operators/#contact-information-of-currently-approved-spv-operators).
Before submitting the first and any consecutive mint proposals, the Minter has to make an Administrative Buffer
available to the SPV Operator. The payment of the Administrative Buffer can be made in Deposit Equivalents. If the SPV
Operator is making use of the Administrative Buffer or parts thereof in accordance with the criteria set out in the
Adopted Guidance, the Minter shall replenish the Administrative Buffer so that it meets or exceeds the defined amount
for the Administrative Buffer before submitting any further mint proposal.
In the interest of the stability of $M and the ecosystem, a BD Minter should provide assurance (solely) to their direct
institutional counterparties that it will buy back $M at par value (minus any applicable fees), within the scale and
time frame appropriate given its business operating conditions.
### 6.4. BD Minter and Minter Compliance Requirements
Each Minter and its affiliates, such as the BD Minter, must have written policies, procedures, and internal controls
designed to ensure compliance with the applicable laws within the jurisdiction in which they operate. Additionally,
Minters need to have anti-money laundering procedures that are reasonably designed to prevent the Minter from
facilitating money laundering and terrorist financing including know-your-customer policies, maintaining highly useful
records for law enforcement and regulators and identifying and reporting suspicious activity, as appropriate. These
controls must be designed to comply with the obligations of the Minter’s jurisdiction of incorporation or with the
international standards recommended by the Financial Action Task Force, as specified below. These obligations can be
fulfilled by the BD Minter on behalf and for the Minter.
Minters need to maintain anti-money laundering and counter terrorist financing (AML/CFT) programs that are reasonably
designed to manage and mitigate the Minter’s risks related to money laundering and terrorist financing. The nature and
extent of the AML/CFT program depends upon a number of factors, including the nature, scale and complexity of the
Minter’s operations, the diversity of its operations, including geographical diversity, its customer base, product and
activity profile, and the degree of risk associated with each area of its operations, among other factors. These
obligations can be fulfilled by the BD Minter on behalf and for the Minter.
As part of its AML/CFT program, Minters need to obtain and verify the customer identification information as required by
the laws of the jurisdiction within which it operates. Minters must also implement policies and procedures to collect
additional information to verify the customer’s identity at the beginning of the customer relationship to demonstrate
that the Minter can form a reasonable belief of the identity of its customer. Minters must also implement policies and
procedures to conduct due diligence about its customer sufficient for the Minter to establish a customer’s risk profile
and conduct ongoing monitoring. Such additional, non-core identity information, could include, for example an IP address
with an associated time stamp; geo-location data; device identifiers; digital asset wallet addresses; and transaction
hashes. Minters may simplify the extent of these due diligence measures where the risk associated with the customer
relationship or activities is lower. These obligations can be fulfilled by the BD Minter on behalf and for the Minter.
Minters need to implement policies and procedures that assist with identifying customers’ unusual or suspicious
movements of funds or transactions indicative of potential involvement in illicit activity. These policies and
procedures should ensure that the Minter can timely identify, investigate, and report customers’ unusual or suspicious
activity in compliance with the requirements and timeframes imposed by the jurisdictions within which they operate.
These obligations can be fulfilled by the BD Minter on behalf and for the Minter.
Minters need to designate a person with the responsibility to ensure compliance with the AML/CFT program and oversee
day-to-day operations concerning such compliance. This person must be empowered with sufficient authority and autonomy
to implement the Minter’s AML/CFT program including access to sufficient resources as needed to mitigate the Minter’s
risks of money laundering and terrorist financing. These obligations can be fulfilled by the BD Minter on behalf and for
the Minter.
Minters need to develop procedures to test the effectiveness of the AML/CFT program commensurate with the Minter’s level
of AML risk exposure. The party designated to test the AML/CFT program must be independent, qualified, unbiased and free
from any conflicting business interests that may influence the outcome of the compliance program test. The Minter’s
policies should include provisions for tracking and remediating weaknesses identified as part of these independent
reviews. These obligations can be fulfilled by the BD Minter on behalf and for the Minter.
BD Minters need to implement a training program to ensure that all employees at the BD Minter, or independent
contractors or third party service providers involved in the BD Minter’s and/or Minter’s operations, understand their
obligations under the AML/CFT program. Such training should include senior management and the board of directors or
other similar governing bodies of the BD Minter and Minter. The training program should be designed to provide
appropriately detailed information to employees based on their level of responsibility regarding the AML/CFT program.
BD Minters and Minters need to, at all times, comply with all economic or financial sanctions or trade embargoes
imposed, administered or enforced by the United States (including those administered by OFAC and the U.S. Department of
State), the EU, and any other government authority with jurisdiction over the BD Minter and Minter. The Minter needs to
maintain effective measures to ensure compliance with, and awareness of, its sanctions-related obligations. This
includes implementing measures to conduct appropriate due diligence on customers’ underlying transactions and the
parties involved by screening such parties against OFAC’s SDN list and EU sanctions and monitoring virtual asset wallet
addresses against such lists. Additionally, these measures will monitor the jurisdictions within which its customers are
domiciled to prevent the Minter from facilitating transactions on behalf of persons residing in any country, region or
territory, or government thereof, that is the subject or target of comprehensive sanctions. These obligations can be
fulfilled by the BD Minter on behalf and for the Minter.
### 6.5. Guidelines for Submission of Permissioning Requests
An application for the permissioning as a Minter requires the whitelisting of the applicant’s public key on the list of
permissioned Minters.
As the most central actor in the ecosystem, Minters shall have a thorough understanding of the Adopted Guidance and all
the ways it impacts their business. It is expected that the approval of Minters by Governance shall involve an in-depth
collaboration with governors so they reach a good level of understanding of the applicant’s abilities.
Before submitting its application, the Minter shall make a KYC report public via appropriate channels, as well as
appropriate proof that the requirements set forth in
[Eligibility Criteria for Permissioned Minters](/home/fundamentals/adopted-guidance/bd-minters/#eligibility-criteria-for-permissioned-minters)
are met:
* Certified copies of the commercial register and/or trade register and/or register of companies or alike, proving that
the company was duly incorporated in the jurisdiction it is providing its services from.
* Proof of who the ultimate beneficial owners (UBO) of the company are, including proof for negative politically exposed
persons (PEP) / sanctions check.
* Certified copies of all required official licenses required to operate the business and to provide the services
outlined in the Adopted Guidance and in the contractual agreements where the Minter is a party to.
* A legally binding declaration that no insolvency, bankruptcy or similar/comparable proceedings are currently pending
or to be anticipated in the foreseeable future in relation to the company.
* Proof that the company is minimally affiliated (via its shareholders) with or (practically) controlled by any SPV
Operator and/or Validator named as approved or permissioned actor in the Adopted Guidance. When there is some level of
affiliation, relevant evidence of appropriate corporate governance shall be presented.
* A legally binding declaration, as long as objectively and legally possible, to set up its business activities in
regards to the services and contractual relationships as outlined in the Adopted Guidance.
* A legally binding commitment, as long as objectively and legally possible, to amend its contractual relationships
reflecting potential mandatory changes of the Adopted Guidance.
Where confidentiality concerns emerge, the M0 Foundation can step under Non-Disclosure Agreements to analyze the
required documentation and provide a qualified public opinion to the ecosystem.
## M0 Documentation Sections
### I. For Stablecoin Builders
#### Build Your Stablecoin on the Most Programmable Platform
M0 is the premier stablecoin platform designed for builders, supporting a network of issuers for multiple, interoperable stablecoins. Start with the core `$M` stablecoin, add your brand, and customize features precisely for your use case.
##### Build Your Way with $M Extensions
* **Customization:** Start with `$M` as your base. Add your branding and tailor features like yield distribution rules, risk and compliance settings, and upgrade management.
* **Guaranteed Clearing:** The M0 platform ensures seamless 1:1 clearing across all official `$M` extensions, maintaining foundational interoperability.
##### Access Shared Liquidity
* **Network Effect:** Tap into the shared liquidity pool of the entire M0 network, benefiting both users and distributors.
* **Native Interoperability:** Stablecoins built as `$M` extensions are inherently interoperable via composable wrapping and unwrapping. Any Minter on the network can mint/redeem them.
* **Simplified Integration:** Infrastructure providers (wallets, on/off-ramps, DeFi) integrate `$M` once for automatic compatibility with all current and future extensions. This unifies liquidity and connects diverse use cases, overcoming the limitations of siloed models.
##### Optionality to Become an M0 Issuer
* **Multi-Issuer Model:** M0 uniquely allows capable entities to connect directly to the protocol and mint `$M` and its extensions.
* **Path to Full Control:** Initially, work with an existing M0 issuer to source primary liquidity for your `$M` extension. The platform provides a future pathway to become an issuer yourself, gaining complete control over your stablecoin solution.
##### Key Infrastructure Features
* **$M Extensions:**
* Build your branded, feature-rich stablecoin in minutes.
* Incorporate desired functionality using `$M` as the core building block.
* Control how collateral yield is utilized for your specific use case.
* Unlock unified liquidity across applications through native interoperability.
* **Stablecoin Orchestration APIs:**
* Easily access primary liquidity for `$M` or your custom extension from the network of M0 issuers.
* Optimize treasury management by connecting directly with M0's primary market.
* Embed stablecoin creation and redemption directly into your application flows.
**Ready to build? Get in touch:**
[https://www.m0.org/stablecoin-builders#contact-section](https://www.m0.org/stablecoin-builders#contact-section)
### II. For Stablecoin Integrators
#### Connect to a Growing Network of Digital Dollar Use Cases
Integrating stablecoins often involves dealing with fragmented liquidity across different tokens. M0 solves this with its unique extensions model, enabling seamless connectivity to a diverse and expanding ecosystem.
##### Integrate Once, Serve Many Use Cases
* **Unified Integration:** By integrating with M0, your platform (wallet, on/off-ramp, DeFi protocol) automatically supports all current and future M0-powered stablecoins.
* **Overcome Fragmentation:** All M0 extensions are natively interoperable 1:1. Maximize the return on your integration effort and provide users with a seamless experience across different digital dollar applications.
##### Easily Monetize Your Crypto Product or Service
* **onchain Yield:** Integrate with M0 to access the best risk-free yield proxy available onchain.
* **Permissionless Rewards:** Get rewarded simply by holding M0-powered stablecoins on your platform. Implement new revenue streams, partner payouts, or user incentives directly onchain, without needing complex offchain agreements.
##### Built-in Support for Any Crypto Architecture
* **Flexible Integration:** Seamlessly integrate reward distribution mechanics into any application type using M0-powered stablecoins.
* **Broad Compatibility:** Whether you run a DeFi protocol, a non-custodial wallet, or a custody service with segregated accounts, M0 infrastructure supports automated onchain tracking and facilitates your desired reward distribution logic.
##### Key Infrastructure Features
* **Stablecoin Orchestration APIs:**
* Easily access primary liquidity for M0-powered stablecoins from the M0 issuer network.
* Optimize treasury management via direct connection to M0's primary market.
* Embed stablecoin creation and redemption directly into your application flows.
* **Highly-Liquid Convertibility:**
* M0 works actively with liquidity providers and on/off-ramp partners to ensure deep, liquid secondary markets against other popular stablecoins like USDC and USDT, catering to user preferences.
* Focus on building a great user experience while benefiting from seamless connectivity to all M0 extensions.
**Ready to integrate? Get in touch:**
[https://www.m0.org/stablecoin-integrators#contact-section](https://www.m0.org/stablecoin-integrators#contact-section)
## Stablecoin Features
M0 empowers you to tailor a digital dollar precisely to your application's needs, leveraging the security and yield of the M0 Protocol while giving you full control over your stablecoin's behavior and features.
An extension is your own smart contract that allows you to define unique rules, branding, and value flows, all built upon M0's pre-determined components and robust infrastructure.
### Key Design Decisions
Before writing code, consider these crucial aspects. With M0 you will be able to customize your extension to fit any needs below. Your choices will define your extension's functionality and value proposition:
#### 1. Purpose & Branding
* What will your stablecoin be used for (e.g., payments, in-app currency, rewards, DeFi collateral)?
* Will it be publicly branded (e.g., YourAppUSD) or an internal/infrastructure component?
#### 2. Access Control
* Will your extension be permissionless (publicly accessible) or restricted to a specific whitelist of users/contracts?
#### 3. Yield Distribution
* If your extension contract is approved as an M0 Earner, it will accrue yield. How will this yield be distributed?
* **As rewards to token holders?** (e.g., via rebasing balances or a claimable mechanism)
* **As revenue to a treasury/foundation/business**?
* **Split between multiple parties?** (e.g., holders and a treasury)
M0's design gives you complete flexibility to implement your desired yield flow.
#### 4. Compliance Features
* There are compliance requirements in all extensions. M0 supports [Predicate](https://predicate.io/) integration as the preferred partner on all EVM chains. You can build these directly into your extension.
#### 5. Multi-Chain Deployment
* What chains does your stablecoin need to live in? Extensions can be deployed across multiple networks. While M0 already supports off-ramping USDC on various chains, this option ensures that the extension token itself can natively exist and be held on other chains as needed.
#### 6. Advanced Yield Management (Optional)
* For more sophisticated use cases, partners can implement advanced yield controls, such as whitelisting eligible yield recipients, defining multiple yield tiers, or redirecting yield from LP contracts to alternate addresses.
**Deeper Dive**: [M0 Extensions](/home/technical-documentations/extensions/)
## Glossary
Glossary of main concepts across the M0 infrastructure.
##### Earner
A holder or distributor of M0-powered stablecoins whose address is approved by governance to earn the Earner Rate.
##### Earner Rate
The annualized percentage paid in the Earn Mechanism.
##### Eligible Collateral
A description of portfolio composition which can be placed in Eligible Custody Solutions and be used to generate an onchain Collateral Value, which is subsequently used to mint M0-powered stablecoins.
##### Eligible Custody Solution
A description of entity structures, jurisdictions, contractual agreements, and other details that will suffice for the custody of Eligible Collateral.
##### Mint Delay
A period of time between when a Minter has called Propose Mint and when they can first call Mint. It serves as a protective measure to ensure all actors have sufficient time to audit each Propose Mint.
##### Mint Ratio
The fraction size of a Minter's onchain Collateral Value that they can mint.
##### Minter
An institution that connects to the protocol to generate and manage stablecoin supply.
##### Minter Freeze Time
A period of time during which a Minter will be disabled to call Propose Mint and Mint methods. Can be called by any Validator on any Minter by passing the Minter address as the argument of the Freeze method. It can be called multiple times on the same Minter to reset the Minter Freeze Time window. If Freeze is called during an already existing Minter Freeze Time, the Minter Freeze Time window will restart from the beginning.
##### Minter Rate
The annualized percentage charged continuously to Minters on their outstanding debt. It is alterable with a Standard Proposal.
##### Penalty Rate
The percentage charged for missing Update Collateral interval(s) or being undercollateralized on outstanding debt that is in excess of the amount a Minter is permitted to have generated. It is assessed any time Accrue Penalty is called, which is embedded in both Update Collateral and Burn. It is alterable with a Standard Proposal. This is a fixed percentage and not an annualized rate.
##### Periphery Contract
A smart contract that adds supplement functionality, but exists outside of the core M0 protocol.
##### `POWER`
One of the two utility tokens used in the M0 TTG. It is used to vote on active proposals and can be considered the primary management token of the mechanism. `POWER` holders will earn `ZERO` in exchange for their direct participation in governance.
##### Propose Mint Time To Live
The amount of time after the Mint Delay during which a Minter can complete the mint operation before Propose Mint expires. It serves as a protective measure to ensure that Minters cannot call Propose Mint and then execute the Mint at a much later date.
##### TTG
TTG stands for Two Token Governor and is a mechanism by which holders of the voting tokens are penalized for failing to vote. There are two utility tokens used in the M0 TTG: `POWER` and `ZERO`.
##### Update Collateral Interval
This amount of time is the period between which Update Collateral must be called by a Minter. If they do not call Update Collateral within this amount of time after their previous call, their onchain Collateral Value is set to 0 and they will incur Penalty Rate on the next update. It is alterable with a Standard Proposal.
##### Validator
An independent entity that provides timely information about the offchain collateral being used to back M0-powered stablecoins.
##### ZERO
One of the two utility tokens used in the M0 TTG, is earned by `POWER` holders in exchange for their direct participation in governance. The `ZERO` holders are the recipient of all remaining value that is not paid out to the Earn Mechanism for their participation in protocol governance. The anticipated accumulation of tokens to the `ZERO` holders are Proposal Fee payments from proposal submission, the payments from `POWER` token auctions, and a portion of Minter Rate and Penalty Rate charges to Minters.
## Disclosures
The information contained in this document is being provided solely for informational/discussion purposes and the reader should not construe anything contained herein to be a solicitation or an offer of sale of securities. Nor should you construe the contents of this document as legal, tax or financial advice. Any potential participant in the M0 ecosystem is urged to consult their own advisors for any legal, tax or financial questions.
To the extent this document contains any forecasts, projections, goals, plans, and other forward-looking statements regarding the M0 project, position, results, and/or other data, you acknowledge that such forward-looking statements are based on our assumptions, estimates, outlook, and other judgments made in light of information available at the time of preparation of such statements and involve both known and unknown risks and uncertainties. Accordingly, any forecasts, plans, goals, and other statements contained herein may not be realized as described, and actual financial results, success/failure or progress of development, and other projections may differ materially from those presented herein.
Neither the $M token, nor any governance tokens associated with the M0 project, will be offered to US persons (without a valid exemption). Any token described in this document has not been registered or qualified under any state or national securities law or regulation.
Participation in the purchase, use, or investment in any digital assets, tokens, or cryptocurrencies involves inherent risks, including but not limited to market volatility, regulatory changes, and technological risks. Prospective investors should conduct their own research and seek the advice of a qualified financial advisor or legal counsel before making any decisions.
The team and contributors associated with this project make no representations, warranties or guarantees regarding the accuracy, completeness, or reliability of the information contained in this whitepaper or any linked materials. They disclaim any liability for any direct, indirect, or consequential losses or damages arising from reliance on this information or any errors or omissions in its content.
By accessing, reading, or using this whitepaper, you acknowledge and agree to the terms outlined in this disclaimer. You are solely responsible for evaluating the risks and merits associated with any actions you take related to the contents of this document.
### M0 Protocol and TTG Smart Contracts
The M0 Protocol and TTG smart contracts went through a series of extensive audits by industry-leading security
companies.
| Auditor | Date | Report |
| --------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Quantstamp | Jan 2024 - March 2024 | [Quantstamp\_report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/Quantstamp%20Audit%20Report.pdf) |
| Three Sigma | Jan 2024 - March 2024 | [ThreeSigma\_report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/ThreeSigma%20Audit%20Report.pdf) |
| Certora | Jan 2024 - March 2024 | [Certora\_report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/Certora%20Audit%20report.pdf) |
| Chainsecurity | Jan 2024 - March 2024 | [Chainsecurity\_report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/ChainSecurity%20Audit%20Report.pdf) |
| OpenZeppelin | Jan 2024 - March 2024 | [OpenZeppelin report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/OpenZeppelin%20Audit%20Report.pdf) |
| Prototech Labs | Jan 2024 - Feb 2024 | [PrototechLabs\_report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/Prototech%20Labs%20Audit%20Report.pdf) |
| Kirill Fedoseev | Dec 2023 - April 2024 | [Kirill Fedoseev report.md](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/Kirill%20Fedoseev%20Independent%20Auditor%20Report.md) |
| Sherlock | March 2024 - April 2024 | [Sherlock\_report.pdf](https://github.com/m0-foundation/documentation/blob/main/protocol-audit-reports/Sherlock%20Audit%20Report.pdf) |
### Wrapped $M (w$M)
Wrapped $M (w$M) went through a series of audits in summer 2024.
| Auditor | Date | Report |
| --------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Chainsecurity | July 2024 - Aug 2024 | [ChainSecurity Wrapped $M Audit Report.pdf](https://github.com/m0-foundation/documentation/blob/main/wrapped-M-audit-reports/ChainSecurity%20Wrapped%20M%20Audit%20Report.pdf) |
| Three Sigma | July 2024 - Aug 2024 | [Three Sigma Wrapped $M Audit Report.pdf](https://github.com/m0-foundation/documentation/blob/main/wrapped-M-audit-reports/ThreeSigma%20Wrapped%20M%20Audit%20Report.pdf) |
| Kirill Fedoseev | July 2024 - Aug 2024 | [Kirill Fedoseev Wrapped $M Audit Report.md](https://github.com/m0-foundation/documentation/blob/main/wrapped-M-audit-reports/Kirill%20Fedoseev%20Audit%20Report.md) |
### EVM M-Extensions
EVM M-Extensions, smart contract extensions for the M0 protocol, went through comprehensive audits in 2025.
| Auditor | Date | Report |
| ------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Certora | July 2025 | [Certora\_MExtension\_report.pdf](https://github.com/m0-foundation/evm-m-extensions/blob/main/audits/Certora%20MExtension%20Security%20Assessment%20Final%20Report.pdf) |
| ChainSecurity | July 2025 | [ChainSecurity\_MExtensions\_report.pdf](https://github.com/m0-foundation/evm-m-extensions/blob/main/audits/ChainSecurity_M0_M_Extensions_audit_draft.pdf) |
| Guardian | August 2025 | [Guardian\_MExtensions\_report.pdf](https://github.com/m0-foundation/evm-m-extensions/blob/main/audits/Guardian%20Audits%20M0%20Extensions%20Report%20Aug%205.pdf) |
### Solana M-Extensions
Solana M-Extensions, the Solana implementation of M0 protocol extensions, underwent thorough security audits in 2025.
| Auditor | Date | Report |
| -------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Halborn | June 2025 | [Halborn\_SolanaExtensions\_report.pdf](https://github.com/m0-foundation/solana-m-extensions/blob/main/audits/halborn_m_extensions_audit_report.pdf) |
| Adevar | July 2025 | [Adevar\_SolanaExtensions\_report.pdf](https://github.com/m0-foundation/solana-m-extensions/blob/main/audits/adevar_m_extensions_audit_report.pdf) |
| Ottersec | July 2025 | [Ottersec\_SolanaExtensions\_report.pdf](https://github.com/m0-foundation/solana-m-extensions/blob/main/audits/ottersec_m_extensions_audit_report.pdf) |
### M Portal Lite
M Portal Lite, the lightweight cross-chain bridge solution, was audited by leading security firms in 2025.
| Auditor | Date | Report |
| --------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Three Sigma | April 2025 | [ThreeSigma\_PortalLite\_report.pdf](https://github.com/m0-foundation/m-portal-lite/blob/main/audits/Three%20Sigma%20-%20M0PortalLite.pdf) |
| ChainLight | May 2025 | [ChainLight\_PortalLite\_report.pdf](https://github.com/m0-foundation/m-portal-lite/blob/main/audits/ChainLight%20-%20M%20Portal%20Lite%20Security%20Audit%20v1.0.pdf) |
| ChainSecurity | September 12, 2025 | [ChainSecurity\_M0\_M\_Portal\_Lite\_audit.pdf](https://github.com/m0-foundation/m-portal-lite/blob/main/audits/ChainSecurity_M0_M_Portal_Lite_audit.pdf) |
| Guardian Audits | August 15, 2025 | [GuardianAudits\_M0\_PortalLite\_audit.pdf](https://github.com/m0-foundation/m-portal-lite/blob/main/audits/GuardianAudits_M0_PortalLite_audit.pdf) |
| Halborn | October 27, 2025 | [Halborn - M Portal Lite 10-27-25.pdf](https://github.com/m0-foundation/m-portal-lite/blob/main/audits/Halborn%20-%20M%20Portal%20Lite%2010-27-25.pdf) |
### Solana M
Solana M, the core M0 protocol implementation on Solana, received comprehensive security reviews in 2025.
| Auditor | Date | Report |
| -------- | ---------- | ----------------------------------------------------------------------------------------------------------------------- |
| Halborn | March 2025 | [Halborn\_SolanaM\_report.pdf](https://github.com/m0-foundation/solana-m/blob/main/audits/halborn_solana_m_audit.pdf) |
| OtterSec | April 2025 | [OtterSec\_SolanaM\_report.pdf](https://github.com/m0-foundation/solana-m/blob/main/audits/ottersec_solana_m_audit.pdf) |
| Sec3 | May 2025 | [Sec3\_SolanaM\_report.pdf](https://github.com/m0-foundation/solana-m/blob/main/audits/sec3_solana_m_audit_report.pdf) |
### mUSD
mUSD, an M0 extension derived from `MYieldToOne`, an upgradeable ERC20 token contract designed to wrap into a
non-rebasing token, where all accrued yield is claimable by a single designated recipient, underwent comprehensive
security audits in August 2025.
| Auditor | Date | Report |
| ------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| ChainSecurity | August 26, 2025 | [ChainSecurity\_M0\_MUSD\_audit.pdf](https://github.com/m0-foundation/mUSD/blob/main/audits/ChainSecurity_M0_MUSD_audit.pdf) |
| Consensys Diligence | August 2025 | [ConsensysDiligence\_M0\_MUSD\_audit.pdf](https://github.com/m0-foundation/mUSD/blob/main/audits/ConsensysDiligence_M0_MUSD_audit.pdf) |
| Guardian Audits | August 15, 2025 | [GuardianAudits\_M0\_MUSD\_report.pdf](https://github.com/m0-foundation/mUSD/blob/main/audits/GuardianAudits_M0_MUSD_report.pdf) |
| Kirill Fedoseev | August 6, 2025 | [MZero-review-report-v1-private.md](https://github.com/m0-foundation/mUSD/blob/main/audits/MZero-review-report-v1-private.md) |
## Deployments
M0 Protocol and TTG were deployed to Ethereum Mainnet on May 7th 2024. Wrapped $M (w$M) was deployed to Ethereum Mainnet
on Aug 14th 2024.
### Ethereum
| Contract | Address | ABI | Repository |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| $M Token | [ 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b ](https://etherscan.io/address/0x866a2bf4e572cbcf37d5071a7a58503bfb36be1b) | [ABI](https://etherscan.io/address/0x866a2bf4e572cbcf37d5071a7a58503bfb36be1b#code#L1) | [MToken](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol) |
| Wrapped $M Token | [ 0x437cc33344a0B27A429f795ff6B469C72698B291 ](https://etherscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291) | [ABI](https://etherscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291#code#L1) | [WrappedMToken](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol) |
| Minter Gateway | [ 0xf7f9638cb444D65e5A40bF5ff98ebE4ff319F04E ](https://etherscan.io/address/0xf7f9638cb444D65e5A40bF5ff98ebE4ff319F04E) | [ABI](https://etherscan.io/address/0xf7f9638cb444D65e5A40bF5ff98ebE4ff319F04E#code#L1) | [MinterGateway](https://github.com/m0-foundation/protocol/blob/main/src/MinterGateway.sol) |
| Minter Rate Model | [ 0xcA144B0Ebf6B8d1dDB5dDB730a8d530fe7f70d62 ](https://etherscan.io/address/0xcA144B0Ebf6B8d1dDB5dDB730a8d530fe7f70d62) | [ABI](https://etherscan.io/address/0xcA144B0Ebf6B8d1dDB5dDB730a8d530fe7f70d62#code#L1) | [MinterRateModel](https://github.com/m0-foundation/protocol/blob/main/src/rateModels/MinterRateModel.sol) |
| Earner Rate Model | [ 0x26d01A2c91f6529aD72d2C27a03d963CAb90dFfd ](https://etherscan.io/address/0x26d01A2c91f6529aD72d2C27a03d963CAb90dFfd) | [ABI](https://etherscan.io/address/0x26d01A2c91f6529aD72d2C27a03d963CAb90dFfd#code#L1) | [EarnerRateModel](https://github.com/m0-foundation/protocol/blob/main/src/rateModels/EarnerRateModel.sol) |
| TTG Registrar | [ 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c ](https://etherscan.io/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c) | [ABI](https://etherscan.io/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c#code#L1) | [Registrar](https://github.com/m0-foundation/ttg/blob/main/src/Registrar.sol) |
| Power Token | [ 0x5983B89FA184f14917013B9C3062afD9434C5b03 ](https://etherscan.io/address/0x5983B89FA184f14917013B9C3062afD9434C5b03) | [ABI](https://etherscan.io/address/0x5983B89FA184f14917013B9C3062afD9434C5b03#code#L1) | [Power token](https://github.com/m0-foundation/ttg/blob/main/src/PowerToken.sol) |
| Zero Token | [ 0x988567FE094570cCE1FFdA29D1f2d842B70492be ](https://etherscan.io/address/0x988567FE094570cCE1FFdA29D1f2d842B70492be) | [ABI](https://etherscan.io/address/0x988567FE094570cCE1FFdA29D1f2d842B70492be#code#L1) | [Zero token](https://github.com/m0-foundation/ttg/blob/main/src/ZeroToken.sol) |
| Standard Governor | [ 0xB024aC5a7c6bC92fbACc8C3387E628a07e1Da016 ](https://etherscan.io/address/0xB024aC5a7c6bC92fbACc8C3387E628a07e1Da016) | [ABI](https://etherscan.io/address/0xB024aC5a7c6bC92fbACc8C3387E628a07e1Da016#code#L1) | [StandardGovernor](https://github.com/m0-foundation/ttg/blob/main/src/StandardGovernor.sol) |
| Emergency Governor | [ 0x886d405949F709bC3f4451491bDd07ff51Cdf90A ](https://etherscan.io/address/0x886d405949F709bC3f4451491bDd07ff51Cdf90A) | [ABI](https://etherscan.io/address/0x886d405949F709bC3f4451491bDd07ff51Cdf90A#code#L1) | [EmergencyGovernor](https://github.com/m0-foundation/ttg/blob/main/src/EmergencyGovernor.sol) |
| Zero Governor | [ 0xa0DAFaEEA4A1d44534e1b9227e19CAE6358b80FE ](https://etherscan.io/address/0xa0DAFaEEA4A1d44534e1b9227e19CAE6358b80FE) | [ABI](https://etherscan.io/address/0xa0DAFaEEA4A1d44534e1b9227e19CAE6358b80FE#code#L1) | [ZeroGovernor](https://github.com/m0-foundation/ttg/blob/main/src/ZeroGovernor.sol) |
| Distribution Vault | [ 0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f ](https://etherscan.io/address/0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f) | [ABI](https://etherscan.io/address/0xd7298f620B0F752Cf41BD818a16C756d9dCAA34f#code#L1) | [DistributionVault](https://github.com/m0-foundation/ttg/blob/main/src/DistributionVault.sol) |
| Hub Portal | [ 0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd ](https://etherscan.io/address/0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd) | [ABI](https://etherscan.io/address/0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd#code#L1) | [HubPortal](https://github.com/m0-foundation/m-portal/blob/main/src/HubPortal.sol) |
| Wormhole Transceiver | [ 0x0763196A091575adF99e2306E5e90E0Be5154841 ](https://etherscan.io/address/0x0763196A091575adF99e2306E5e90E0Be5154841) | [ABI](https://etherscan.io/address/0x0763196A091575adF99e2306E5e90E0Be5154841#code#L1) | |
| Swap Facility | [ 0xB6807116b3B1B321a390594e31ECD6e0076f6278 ](https://etherscan.io/address/0xB6807116b3B1B321a390594e31ECD6e0076f6278) | [ABI](https://etherscan.io/address/0xB6807116b3B1B321a390594e31ECD6e0076f6278#code#L1) | [SwapFacility](https://github.com/m0-foundation/evm-m-extensions/blob/main/src/swap/SwapFacility.sol) |
### Arbitrum
| Contract | Address | ABI | Repository |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| $M Token | [ 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b ](https://arbiscan.io/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b) | [ABI](https://arbiscan.io/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b#code#L1) | [MToken](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol) |
| Wrapped $M Token | [ 0x437cc33344a0B27A429f795ff6B469C72698B291 ](https://arbiscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291) | [ABI](https://arbiscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291#code#L1) | [WrappedMToken](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol) |
| Registrar | [ 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c ](https://arbiscan.io/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c) | [ABI](https://arbiscan.io/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c#code#L1) | [Registrar](https://github.com/m0-foundation/ttg/blob/main/src/Registrar.sol) |
| Spoke Portal | [ 0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd ](https://arbiscan.io/address/0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd) | [ABI](https://arbiscan.io/address/0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd#code#L1) | [SpokePortal](https://github.com/m0-foundation/m-portal/blob/main/src/SpokePortal.sol) |
| Wormhole Transceiver | [ 0x0763196A091575adF99e2306E5e90E0Be5154841 ](https://arbiscan.io/address/0x0763196A091575adF99e2306E5e90E0Be5154841) | [ABI](https://arbiscan.io/address/0x0763196A091575adF99e2306E5e90E0Be5154841#code#L1) | |
| Spoke Vault | [ 0x3349e443068F76666789C4f76F00D9c4F38A4DdE ](https://arbiscan.io/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE) | [ABI](https://arbiscan.io/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE#code#L1) | [SpokeVault](https://github.com/m0-foundation/m-portal/blob/main/src/SpokeVault.sol) |
### Optimism
| Contract | Address | ABI | Repository |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| $M Token | [ 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b ](https://optimistic.etherscan.io/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b) | [ABI](https://optimistic.etherscan.io/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b#code#L1) | [MToken](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol) |
| Wrapped $M Token | [ 0x437cc33344a0B27A429f795ff6B469C72698B291 ](https://optimistic.etherscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291) | [ABI](https://optimistic.etherscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291#code#L1) | [WrappedMToken](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol) |
| Registrar | [ 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c ](https://optimistic.etherscan.io/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c) | [ABI](https://optimistic.etherscan.io/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c#code#L1) | [Registrar](https://github.com/m0-foundation/ttg/blob/main/src/Registrar.sol) |
| Spoke Portal | [ 0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd ](https://optimistic.etherscan.io/address/0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd) | [ABI](https://optimistic.etherscan.io/address/0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd#code#L1) | [SpokePortal](https://github.com/m0-foundation/m-portal/blob/main/src/SpokePortal.sol) |
| Wormhole Transceiver | [ 0x0763196A091575adF99e2306E5e90E0Be5154841 ](https://optimistic.etherscan.io/address/0x0763196A091575adF99e2306E5e90E0Be5154841) | [ABI](https://optimistic.etherscan.io/address/0x0763196A091575adF99e2306E5e90E0Be5154841#code#L1) | |
| Spoke Vault | [ 0x3349e443068F76666789C4f76F00D9c4F38A4DdE ](https://optimistic.etherscan.io/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE) | [ABI](https://optimistic.etherscan.io/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE#code#L1) | [SpokeVault](https://github.com/m0-foundation/m-portal/blob/main/src/SpokeVault.sol) |
### Plume
| Contract | Address | ABI | Repository |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| $M Token | [ 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b ](https://explorer.plume.org/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b) | [ABI](https://optimistic.etherscan.io/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b#code#L1) | [MToken](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol) |
| Wrapped $M Token | [ 0x437cc33344a0B27A429f795ff6B469C72698B291 ](https://explorer.plume.org/address/0x437cc33344a0B27A429f795ff6B469C72698B291) | [ABI](https://optimistic.etherscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291#code#L1) | [WrappedMToken](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol) |
| Registrar | [ 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c ](https://explorer.plume.org/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c) | [ABI](https://explorer.plume.org/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c#code#L1) | [Registrar](https://github.com/m0-foundation/ttg/blob/main/src/Registrar.sol) |
| Spoke Portal | [ 0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE ](https://explorer.plume.org/address/0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE) | [ABI](https://explorer.plume.org/0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE#code#L1) | [SpokePortal](https://github.com/m0-foundation/m-portal-lite/blob/main/src/SpokePortal.sol) |
| Spoke Vault | [ 0x3349e443068F76666789C4f76F00D9c4F38A4DdE ](https://explorer.plume.org/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE) | [ABI](https://explorer.plume.org/0x3349e443068F76666789C4f76F00D9c4F38A4DdE#code#L1) | [SpokeVault](https://github.com/m0-foundation/m-portal-lite/blob/main/src/SpokeVault.sol) |
| Bridge | [ 0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3 ](https://explorer.plume.org/address/0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3) | [ABI](https://explorer.plume.org/0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3#code#L1) | |
### HyperEVM
| Contract | Address | ABI | Repository |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| $M Token | [ 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b ](https://purrsec.com/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b) | [ABI](https://purrsec.com/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b#code#L1) | [MToken](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol) |
| Wrapped $M Token | [ 0x437cc33344a0B27A429f795ff6B469C72698B291 ](https://purrsec.com/address/0x437cc33344a0B27A429f795ff6B469C72698B291) | [ABI](https://purrsec.com/address/0x437cc33344a0B27A429f795ff6B469C72698B291#code#L1) | [WrappedMToken](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol) |
| Registrar | [ 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c ](https://purrsec.com/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c) | [ABI](https://purrsec.com/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c#code#L1) | [Registrar](https://github.com/m0-foundation/ttg/blob/main/src/Registrar.sol) |
| Portal | [ 0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE ](https://purrsec.com/address/0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE) | [ABI](https://purrsec.com/address/0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE#code#L1) | [Portal](https://github.com/m0-foundation/m-portal-lite/blob/main/src/Portal.sol) |
| Vault | [ 0x3349e443068F76666789C4f76F00D9c4F38A4DdE ](https://purrsec.com/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE) | [ABI](https://purrsec.com/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE#code#L1) | [Vault](https://github.com/m0-foundation/m-portal-lite/blob/main/src/Vault.sol) |
| Bridge | [ 0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3 ](https://purrsec.com/address/0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3) | [ABI](https://purrsec.com/address/0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3#code#L1) | |
### Plasma
| Contract | Address | ABI | Repository |
| --------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| M Token | [ 0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b ](https://plasmascan.to/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b) | [ABI](https://plasmascan.to/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b#code) | [MToken](https://github.com/m0-foundation/protocol/blob/main/src/MToken.sol) |
| Wrapped M Token | [ 0x437cc33344a0B27A429f795ff6B469C72698B291 ](https://plasmascan.to/address/0x437cc33344a0B27A429f795ff6B469C72698B291) | [ABI](https://plasmascan.to/address/0x437cc33344a0B27A429f795ff6B469C72698B291#code) | [WrappedMToken](https://github.com/m0-foundation/wrapped-m-token/blob/v2/src/WrappedMToken.sol) |
| Registrar | [ 0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c ](https://plasmascan.to/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c) | [ABI](https://plasmascan.to/address/0x119FbeeDD4F4f4298Fb59B720d5654442b81ae2c#code) | [Registrar](https://github.com/m0-foundation/ttg/blob/main/src/Registrar.sol) |
| Spoke Portal | [ 0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE ](https://plasmascan.to/address/0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE) | [ABI](https://plasmascan.to/address/0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE#code) | [SpokePortal](https://github.com/m0-foundation/m-portal-lite/blob/main/src/SpokePortal.sol) |
| Spoke Vault | [ 0x3349e443068F76666789C4f76F00D9c4F38A4DdE ](https://plasmascan.to/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE) | [ABI](https://plasmascan.to/address/0x3349e443068F76666789C4f76F00D9c4F38A4DdE#code) | [SpokeVault](https://github.com/m0-foundation/m-portal-lite/blob/main/src/SpokeVault.sol) |
| Bridge | [ 0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3 ](https://plasmascan.to/address/0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3) | [ABI](https://plasmascan.to/address/0x51DcE104E5ba88fabC19A2C519f955bb834b0DC3#code) | |
| Swap Facility | [ 0xB6807116b3B1B321a390594e31ECD6e0076f6278 ](https://plasmascan.to/address/0xB6807116b3B1B321a390594e31ECD6e0076f6278) | [ABI](https://plasmascan.to/address/0xB6807116b3B1B321a390594e31ECD6e0076f6278#code) | [SwapFacility](https://github.com/m0-foundation/evm-m-extensions/blob/main/src/swap/SwapFacility.sol) |
### Solana
#### Mainnet
| Program/Token | Address | Explorer | Repository |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| M Token | [ mzerokyEX9TNDoK4o2YZQBDmMzjokAeN6M2g2S3pLJo ](https://explorer.solana.com/address/mzerokyEX9TNDoK4o2YZQBDmMzjokAeN6M2g2S3pLJo) | [View](https://explorer.solana.com/address/mzerokyEX9TNDoK4o2YZQBDmMzjokAeN6M2g2S3pLJo) | [solana-m](https://github.com/m0-foundation/solana-m) |
| Wrapped M Token | [ mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp ](https://explorer.solana.com/address/mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp) | [View](https://explorer.solana.com/address/mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp) | [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions) |
| Portal | [ mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY ](https://explorer.solana.com/address/mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY) | [View](https://explorer.solana.com/address/mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY) | |
| Transceiver | [ J1bVGcwG3nPsAJsi3GFNqC9NZmKatSuoutPbaKMiT7Bm ](https://explorer.solana.com/address/J1bVGcwG3nPsAJsi3GFNqC9NZmKatSuoutPbaKMiT7Bm) | [View](https://explorer.solana.com/address/J1bVGcwG3nPsAJsi3GFNqC9NZmKatSuoutPbaKMiT7Bm) | |
| Quoter | [ Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ ](https://explorer.solana.com/address/Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ) | [View](https://explorer.solana.com/address/Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ) | |
#### Devnet
| Program/Token | Address | Explorer | Repository |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| M Token | [ mzeroZRGCah3j5xEWp2Nih3GDejSBbH1rbHoxDg8By6 ](https://explorer.solana.com/address/mzeroZRGCah3j5xEWp2Nih3GDejSBbH1rbHoxDg8By6?cluster=devnet) | [View](https://explorer.solana.com/address/mzeroZRGCah3j5xEWp2Nih3GDejSBbH1rbHoxDg8By6?cluster=devnet) | [solana-m](https://github.com/m0-foundation/solana-m) |
| Wrapped M Token | [ mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp ](https://explorer.solana.com/address/mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp?cluster=devnet) | [View](https://explorer.solana.com/address/mzeroXDoBpRVhnEXBra27qzAMdxgpWVY3DzQW7xMVJp?cluster=devnet) | [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions) |
| Portal | [ mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY ](https://explorer.solana.com/address/mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY?cluster=devnet) | [View](https://explorer.solana.com/address/mzp1q2j5Hr1QuLC3KFBCAUz5aUckT6qyuZKZ3WJnMmY?cluster=devnet) | |
| Transceiver | [ J1bVGcwG3nPsAJsi3GFNqC9NZmKatSuoutPbaKMiT7Bm ](https://explorer.solana.com/address/J1bVGcwG3nPsAJsi3GFNqC9NZmKatSuoutPbaKMiT7Bm?cluster=devnet) | [View](https://explorer.solana.com/address/J1bVGcwG3nPsAJsi3GFNqC9NZmKatSuoutPbaKMiT7Bm?cluster=devnet) | |
| Quoter | [ Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ ](https://explorer.solana.com/address/Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ?cluster=devnet) | [View](https://explorer.solana.com/address/Nqd6XqA8LbsCuG8MLWWuP865NV6jR1MbXeKxD4HLKDJ?cluster=devnet) | |
## Protocol Mechanics & Reward Programmability
The M0 protocol is the platform powering builders of safe, programmable, interoperable stablecoins. It introduces a superior coordination mechanism that democratizes access to the generation and management of programmable, digital cash instruments. There are a few key areas to understand how value flows through the protocol:
### Eligible Collateral
With the goal of maintaining a standard issuance layer, as well as ensuring stablecoins built on M0 are interoperable, M0 prescribes a list of portfolio components that are deemed eligible as reserve, or collateral, for stablecoins minted on the M0 network. These assets must be held in legally isolated SPVs or similarly bankruptcy-remote structures with independent Validators confirming daily their fair market value on-chain.
The spirit of those eligibility criteria remains that of ensuring that only the safest and regulatory aligned assets will continue to constitute the value reserve, limiting regulatory arbitrage in a scenario of international issuance. While reserves remain fully orphaned and issuer-specific as part of the protocol-enabled issuance, M0 has the ability to force issuance to rely on the most stringent standards in the interest of the end customers. The current US regulatory landscape, thanks to the regulatory advancement brought forward by the GENIUS act, is running in that direction.
### Mint Ratio
The mint ratio defines the percentage of a minter's collateral value that can be used to generate M0-powered stablecoins. It sets the required level of over-collateralization for each issuer and ensures that the system remains fully backed while accounting for operational and risk management needs.
### Minter Rate
M0 is an accrual-based, rather than cash-based, system. The minter rate is a universal, continuously compounding rate applied to each minter's outstanding issued supply on the network. This rate is typically calibrated below the yield possibly obtained from Eligible Collateral to allow for coverage of operational costs and ensure there is a viable business for issuers.
### Earner Rate
The protocol also sets an *Earner Rate* paid (only) to approved Earners on their holdings. Smart contracts ensure that value paid out via the Earner Rate does not exceed what accrued via the Minter Rate. When set equal to the Minter Rate—and 100% of holdings sit in approved earning extensions—100% of Minters' payments flow through as yield to Earners, continuously and programmatically.
### Automatic Fee Distribution
All fees accumulate within the protocol and are redistributed automatically via smart contracts. Minters do not need to make manual payments. Instead, protocol logics take care of internal accounting (indexing) and ensure that all risk and reward parameters flow consistently across all the different environments where M0-powered stablecoins exist.
The superiority of an on-chain native, rather than off-chain manual, reward streaming system grows exponentially with the nesting of reward distribution logics, and the omni-chain expansion of all those use cases. Effectively, universal indexing allows the seamless management of exponentially complex reward streaming configuration.
### Extension Yield Allocation
Each extension defines how yield is allocated—whether directly to users, to a treasury, or through customized logic. The protocol continuously streams rewards on-chain, allowing balances to grow incrementally instead of through periodic payouts. This transparency and programmability enable flexible product designs, such as real-time savings or incentive systems tailored to each partner's needs.
import ZoomableImage from "@/components/ZoomableImage";
## M0 is the universal stablecoin platform.
#### With M0, developers can build their own application-specific digital dollars and embed those into any use case.
export function CardGrid() {
const cards = [
{
title: 'Build your Stablecoin',
description: 'M0 provides battle-tested, audited contract models that serve as powerful starting points, allowing you to focus on innovation rather than reinventing the wheel.',
href: '/build/overview/',
iconClass: 'homepageCardImage-BuildYourStablecoin',
},
{
title: 'Protocol Overview',
description: 'Detailed technical architecture, and smart contract specifications that define how the M0 ecosystem functions onchain and offchain.',
href: '/home/overview/',
iconClass: 'homepageCardImage-ProtocolOverview',
},
]
return (
)
}
### The M0 Model
Stablecoins as a concept emerged a decade ago with the simple goal of representing a dollar on the blockchain. The initial wave of products in the sector helped settle trillions of dollars in crypto capital markets transactions over the last number of years.
But money is critical infrastructure, not a product. The initial generation of stablecoins was too simplistic in its design, and a new financial system simply can't be rewritten on top of it. M0 was founded to reconstruct the monetary stack from first principles, enabling a new wave of digital dollar applications to be built.
Application developers value control and the ability to customize their technology stack for their context. One can think of fintech as the customization of dollars into a particular form-factor that favors the use case. Stablecoins allow for onchain programmability of digital dollar behavior. But one-size-fits-all stablecoins issued by centralized actors do not really allow for programmability. The M0 platform was built to help developers create and program their own bespoke digital dollars.
Money, as the key infrastructure that it is, should not belong to a single, monolithic company. M0 was built as an open and federated network. Multiple [Minters](/get-started/resources/glossary#minter) can connect to the M0 protocol and pledge [eligible collateral](/home/fundamentals/adopted-guidance/eligible-collateral/) in order to mint stablecoins. [Validators](/get-started/resources/glossary#validator) independently verify the presence of eligible collateral and guarantee transparency across the network. The M0 protocol ensures purity of the reserves for M0 stablecoins, as well as provides onchain clearing and interoperability across all the platform's custom stablecoins.
## The M0 Ecosystem
Before diving into building, take your time to grasp how M0 works at a high level.
### What is M0?
The M0 platform consists of the M0 protocol, a liquidity delivery layer, and a network of appropriately regulated issuers that enable the creation and operation of so-called stablecoin *extensions*. M0 provides the foundational layer for building programmable, compliant, and interoperable digital currencies.
### The M0 Protocol
The M0 protocol provides the base infrastructure for issuance, redemption, and programmability while allowing builders to define their own parameters for access, compliance, and yield/rewards all on-chain. Each extension operates independently but benefits from shared liquidity, governance, and cross-chain infrastructure within the broader M0 ecosystem. At its core, the M0 protocol offers an on-chain native, immutable foundation for building stable, customizable digital assets that can integrate seamlessly across networks and use cases.
### Issuance
M0 has a permissioned network of appropriately regulated entities that mint M0 extensions under strict collateral, compliance, and operational standards. These issuers hold reserves and follow detailed M0 onboarding requirements to ensure safety, consistency, and legal compliance.
### Extensions
Extensions are programmable stablecoins built on top of the M0 protocol. Each extension can define its own branding and policies for access control, compliance, and rewards distribution. Builders can tailor extensions to specific use cases, from institutional products with strict KYC requirements to consumer-facing assets with on-chain reward streaming. Extensions can have multiple issuers, and can be designed for seamless interoperability, allowing users and partners to interact across products within a unified framework.
### Liquidity Network
M0 provides a shared liquidity delivery system that ensures smooth movement between M0-powered stablecoins and major existing stablecoins (USDC, USDT, etc.). It ensures that anyone using an M0 extension can easily convert into and out of it from other stablecoins across multiple chains.
## Cross Chain Interoperability
### M0 on EVM
M0 is designed for a multichain world, enabling M0 extensions to be accessible across various blockchain ecosystems while maintaining Ethereum as the authoritative source for governance.
#### M-Portals
These are smart contracts facilitating the secure transfer of M0 Extensions (and critical M0 metadata like the yield index and governance parameters) between Ethereum and Spoke chains. To achieve this, M0 integrates with leading interoperability protocols.
Currently, M0 supports M-Portals built on:
* **Wormhole:** Leveraging its Native Token Transfer (NTT) framework.
* **Hyperlane:** Utilizing its [general message passing](https://docs.hyperlane.xyz/docs/protocol/core/mailbox) for permissionless bridging.
If your desired blockchain is not yet supported through these providers, please reach out to discuss custom integration possibilities.
**Deeper Dive**: [M-Portals (Cross-Chain Architecture)](/home/technical-documentations/m-portal/overview/)
### M0 on Solana
The M0 Protocol extends its reach beyond EVM chains by offering a native deployment on the Solana blockchain. This provides the Solana ecosystem with direct access to a secure, yield-bearing stablecoin building block, unlocking new possibilities for high-performance DeFi, payments, and other applications.
This isn't just a wrapped asset; it's a native representation, inheriting its core properties of security and yield while leveraging Solana's speed and low transaction costs.
#### How It Works: A High-Level View
M0's presence on Solana is facilitated by the **M Portal**, which is built on **Wormhole's Native Token Transfer (NTT)** framework. This follows the same hub-and-spoke model used for EVM chains:
1. **Lock on Ethereum**: To move Extensions to Solana, the native token is locked in the `HubPortal` contract on Ethereum.
2. **Message with Wormhole**: A secure message is sent via the Wormhole network to Solana.
3. **Mint on Solana**: The Portal program on Solana receives the message and mints an equivalent amount of native Solana extension (an SPL token).
This process ensures that every extension on Solana is fully backed by a corresponding token locked on Ethereum, maintaining the integrity of the total supply.
#### Key Features for Solana Users & Developers
* **Native Yield**: Extensions on Solana is designed to be yield-bearing. The earning index from Ethereum is propagated via Wormhole, allowing Solana holders to benefit from the same underlying collateral yield.
* **High Performance**: Leverage Solana's sub-second finality and low fees for transactions.
* **Deep Integration**: Extensions have the ability to be a native SPL token or follow token 2022 for more advanced functionality. This means that extensions can be seamlessly integrated into Solana's vibrant DeFi ecosystem, including DEXs, lending protocols, and derivatives platforms.
#### Program Addresses
You can find the relevant Solana program and token addresses in our central address registry:
* [Solana Mainnet & Devnet Addresses](/get-started/resources/addresses#solana)
## Accessing Liquidity
A key benefit of the M0 platform is the shared liquidity among all extensions. This provides the most flexible and lowest friction access to liquidity for your extension.
### Source Liquidity
There are two key ways to source liquidity:
#### 1. Primary Liquidity (Direct with Minters)
For large-scale minting/redeeming of your extension directly against supported stablecoins or USD. This involves a KYB process with an approved M0 Minter. Suitable for centralized businesses and zero-slippage constraints.
#### 2. Onchain Liquidity (DEXs)
Through deep liquidity pools on major chains like Ethereum, Solana, and various L2s. These pools are typically against USDC and aim for efficient pricing. Ideal for decentralized applications and 24x7 access.
| Dex Name | Pool Link |
| :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Uniswap | [ethereum/0x970A7749EcAA4394C8B2Bf5F2471F41FD6b79288](https://app.uniswap.org/explore/pools/ethereum/0x970A7749EcAA4394C8B2Bf5F2471F41FD6b79288) |
| Uniswap | [arbitrum/4464133](https://app.uniswap.org/positions/v3/arbitrum/4464133) |
| Raydium | [pool\_id=CsMzKUUJNoAoU7N4zh3hAS6qcByU81TcQMPJqCdmmcEF](https://raydium.io/clmm/create-position/?pool_id=CsMzKUUJNoAoU7N4zh3hAS6qcByU81TcQMPJqCdmmcEF) |
## Build Your Stablecoin
**M0 Extensions** are the core of our platform's promise. They are custom, feature-rich stablecoins you can create. Instead of a one-size-fits-all approach, M0 provides the raw material for you to craft a digital dollar perfectly suited to your application, brand, and economic model.
This section is your guide to that process. We provide battle-tested, audited contract models that serve as powerful starting points, allowing you to focus on innovation rather than reinventing the wheel.
### Start building
export function Step({ num, heading, content, isLast = false }) {
return(
)
}
export function Button({ text, link }) {
return(
{ text }
)
}
export function CardGrid() {
const cards = [
{
title: 'Treasury',
description: 'Simple & Direct. All accrued yield is sent to a single, designated wallet.',
href: '/build/models/treasury/overview/',
iconClass: 'icon-Treasury',
},
{
title: 'User Yield',
description: 'Flexible & Shared. Yield is passed through to token holders, with an optional protocol fee taken out.',
href: '/build/models/user-yield/overview/',
iconClass: 'icon-UserYield',
},
{
title: 'Institutional',
description: 'Yield is distributed to individually whitelisted accounts, each with its own custom fee rate.',
href: '/build/models/institutional/overview/',
iconClass: 'icon-Institutional',
},
]
return (
Our team will reach out to guide your integration and help you gain early visibility and earner approval.
>
}
/>
The primary difference between our pre-built models is how they manage and distribute
yield. Understanding this is key to choosing the right foundation for your project.
M0 provides powerful, pre-built templates to get you to market faster. These models are audited, battle-tested starting points for the most common use cases. Each one offers a different approach to handling the underlying yield generated by eligible collateral, giving you the flexibility to build the exact product you need.
>
}
/>
This guide provides a step-by-step walkthrough for deploying your own M0 Extension. It assumes you have already chosen this model and understand its architecture.
>
}
/>
For your M0 Extension to accrue yield, its deployed contract address must be approved as an M0 Earner. This is a fundamental security and economic feature of the M0 protocol, ensuring that yield is distributed only to recognized and approved participants.
This approval is not automatic; it is granted through a formal, on-chain M0 Governance process. This guide will walk you through the necessary steps to prepare and submit a governance proposal to get your extension approved.
Once your extension is deployed and approved as an earner, users can convert
into your extension (and back) through the SwapFacility.
This happens automatically - you don't need to build any additional interfaces.
Users interact with your extension through the M0 ecosystem's SwapFacility
contract, which ensures all conversions are 1:1 and secure. Integration partners
like DEX aggregators and wallet providers will handle the user experience.
Model-Specific Integration Guides:
>
}
isLast
/>
## Implementing the User Yield Model
This guide provides step-by-step instructions for deploying a User Yield Model stablecoin, where yield is automatically
distributed to all token holders with an optional protocol fee.
If you haven't already, please review the [**User Yield Model Deep Dive**](/build/models/user-yield/overview/) for
conceptual details about this model and its implementations.
### EVM Implementation
This section covers deploying the `MYieldFee` contract on EVM chains (Ethereum, Base, Arbitrum, etc.).
#### 1. Setup Your Environment
First, you'll need to clone the `m-extensions` repository and set up your development environment:
1. **Clone the repository:**
```bash
git clone https://github.com/m0-foundation/m-extensions.git
cd m-extensions
```
2. **Follow the setup instructions:** Refer to the
[**README.md**](https://github.com/m0-foundation/m-extensions/blob/main/README.md) and follow all the commands
provided to install dependencies and configure your development environment.
3. **Configure Environment Variables:** Create a `.env` file in the root directory:
```bash
# Private key for deployment (include 0x prefix)
PRIVATE_KEY=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
# RPC URLs
MAINNET_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/your-api-key
SEPOLIA_RPC_URL=https://eth-sepolia.alchemyapi.io/v2/your-api-key
# Etherscan API key for verification
ETHERSCAN_API_KEY=your-etherscan-api-key
```
**Security Warning:** Never commit your `.env` file to version control. Add it to your `.gitignore`:
```bash
echo ".env" >> .gitignore
```
Next, decide if you are deploying to L1 (`MYieldFee.sol`) or an L2 (`MSpokeYieldFee.sol`). The implementation steps are
similar, but you will import a different base contract.
#### 2. Create and Initialize Your Contract
Your contract will inherit from `MYieldFee` or `MSpokeYieldFee`. The initializer is key to configuring your token's
economics, especially the `feeRate`.
Create a new file in the `src/` directory, for example, `YieldUSD.sol`.
##### For L1 (Ethereum) Deployment:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { MYieldFee } from "./projects/yieldToAllWithFee/MYieldFee.sol";
contract YieldUSD is MYieldFee {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address mToken_, address swapFacility_) MYieldFee(mToken_, swapFacility_) {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol,
uint16 feeRate_,
address feeRecipient_,
address admin,
address feeManager,
address claimRecipientManager
) public initializer {
MYieldFee.initialize(
name, // "Yield Bearing USD"
symbol, // "yUSD"
feeRate_, // Fee rate in bps (e.g., 1000 = 10%)
feeRecipient_, // Protocol treasury address
admin, // Admin multisig address
feeManager, // Fee manager address
claimRecipientManager // Claim recipient manager address
);
}
}
```
##### For L2 (Spoke Chain) Deployment:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { MSpokeYieldFee } from "./projects/yieldToAllWithFee/MSpokeYieldFee.sol";
contract YieldUSD is MSpokeYieldFee {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address mToken_, address swapFacility_, address rateOracle_)
MSpokeYieldFee(mToken_, swapFacility_, rateOracle_) {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol,
uint16 feeRate_,
address feeRecipient_,
address admin,
address feeManager,
address claimRecipientManager
) public initializer {
MSpokeYieldFee.initialize(
name, // "Yield Bearing USD"
symbol, // "yUSD"
feeRate_, // Fee rate in bps (e.g., 1000 = 10%)
feeRecipient_, // Protocol treasury address
admin, // Admin multisig address
feeManager, // Fee manager address
claimRecipientManager // Claim recipient manager address
);
}
}
```
##### Key Parameters Explained:
* **`name`**: The full name of your token (e.g., "Yield Bearing USD")
* **`symbol`**: The token symbol (e.g., "yUSD")
* **`feeRate_`**: Protocol fee in basis points (0-10,000). E.g., 1000 = 10% fee, 90% to users
* **`feeRecipient_`**: Address that receives the protocol fees
* **`admin`**: Address with `DEFAULT_ADMIN_ROLE` (should be a multisig)
* **`feeManager`**: Address with `FEE_MANAGER_ROLE`
* **`claimRecipientManager`**: Address with `CLAIM_RECIPIENT_MANAGER_ROLE`
**Note:** The `mToken`, `swapFacility`, and `rateOracle` (L2 only) addresses are set in the constructor as immutable
variables, not in the initializer.
#### 3. Write Deployment Script
Create a deployment script in the `script/` directory:
For L1 & L2 Deployment:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Script } from "forge-std/Script.sol";
import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol";
import { YieldUSD } from "../src/YieldUSD.sol";
contract DeployYieldUSD is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
// Deploying a Transparent Proxy, which requires a proxy admin.
// This pattern applies to both L1 (MYieldFee) and L2 (MSpokeYieldFee) deployments.
address proxy = Upgrades.deployTransparentProxy(
"YieldUSD.sol", // Contract file name
deployer, // The admin for the proxy contract itself
abi.encodeCall( // The initializer call data
YieldUSD.initialize,
(
"Yield Bearing USD", // name
"yUSD", // symbol
1000, // feeRate (10%)
0x..., // feeRecipient
0x..., // admin (DEFAULT_ADMIN_ROLE for the logic)
0x..., // feeManager
0x... // claimRecipientManager
)
)
);
vm.stopBroadcast();
console.log("YieldUSD deployed at:", proxy);
}
}
```
#### 4. Testing Your Contract
Before deployment, create comprehensive tests, especially for yield claiming and fee calculations:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Test } from "forge-std/Test.sol";
import { YieldUSD } from "../src/YieldUSD.sol";
contract YieldUSDTest is Test {
YieldUSD token;
address admin = makeAddr("admin");
address feeRecipient = makeAddr("feeRecipient");
address user1 = makeAddr("user1");
address user2 = makeAddr("user2");
function setUp() public {
token = new YieldUSD(
address(mockMToken), // $M Token address
address(mockSwapFacility) // SwapFacility address
// address(mockRateOracle) // For L2 only
);
token.initialize(
"Yield Bearing USD",
"yUSD",
1000, // 10% fee rate
feeRecipient, // Fee recipient
admin, // Admin
admin, // Fee manager
admin // Claim recipient manager
);
}
function testYieldAccrual() public {
// Test that yield accrues correctly to users
// Mock $M token yield accrual
// Check accruedYieldOf for users
}
function testFeeCollection() public {
// Test that protocol fees are collected correctly
// Mock yield accrual
// Call claimFee()
// Assert fee goes to feeRecipient
}
function testYieldClaiming() public {
// Test that users can claim their yield
// Mock yield accrual
// Call claimYieldFor(user)
// Assert user receives correct amount
}
function testIndexUpdating() public {
// Test that index updates correctly
uint128 indexBefore = token.currentIndex();
// Mock time passage and rate changes
token.updateIndex();
uint128 indexAfter = token.currentIndex();
assertGt(indexAfter, indexBefore);
}
}
```
#### 5. Gain M0 Earner Approval
For your contract to accrue yield, its deployed address must be approved as an M0 Earner via a governance vote.
Follow the process outlined in the [Gaining Earner Approval](/build/gaining-earner-approval/) guide.
#### 6. Security & Audit
Security is critical for a user-facing yield product.
* Thoroughly test all functions, especially yield claiming and fee calculations
* Run static analysis tools like Slither
* Pay special attention to the continuous indexing math
* Consider obtaining an independent security audit for your implementation
#### 7. Deploy & Launch
##### Deploy to Testnet First
```bash
# Load environment variables
source .env
# Deploy to Sepolia
forge script script/DeployYieldUSD.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Deploy to Mainnet
```bash
# Load environment variables
source .env
# Deploy to Mainnet
forge script script/DeployYieldUSD.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Alternative: Hardware Wallet Deployment
For production deployments, consider using a hardware wallet:
```bash
forge script script/DeployYieldUSD.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--ledger \
--broadcast
```
##### Enable Earning
Once deployed and approved, call `enableEarning()` on your contract to start yield accrual:
```solidity
// This initializes the index and starts yield accrual
yieldUSD.enableEarning();
```
#### 8. Integration & Usage
##### For Your Application Users
1. **Wrapping into Your Yield Token:**
```solidity
// User approves SwapFacility to spend their tokens
mToken.approve(swapFacility, amount);
// SwapFacility calls your contract's wrap function
swapFacility.swapInM(yieldUSDAddress, amount, recipient);
```
2. **Claiming Yield:**
```solidity
// Anyone can claim yield for a user
uint256 yieldClaimed = yieldUSD.claimYieldFor(userAddress);
```
3. **Checking Balances:**
```solidity
// Current balance (excluding unclaimed yield)
uint256 balance = yieldUSD.balanceOf(userAddress);
// Balance including unclaimed yield
uint256 totalBalance = yieldUSD.balanceWithYieldOf(userAddress);
// Just the unclaimed yield
uint256 yield = yieldUSD.accruedYieldOf(userAddress);
```
##### For Protocol Management
1. **Claiming Protocol Fees:**
```solidity
// Anyone can trigger fee collection
uint256 feeClaimed = yieldUSD.claimFee();
```
2. **Managing Fee Rate:**
```solidity
// Only FEE_MANAGER_ROLE can adjust fees
yieldUSD.setFeeRate(1500); // Change to 15%
```
3. **Managing Fee Recipient:**
```solidity
// Only FEE_MANAGER_ROLE can change recipient
yieldUSD.setFeeRecipient(newTreasuryAddress);
```
4. **Managing Claim Recipients:**
```solidity
// Only CLAIM_RECIPIENT_MANAGER_ROLE can redirect yield
yieldUSD.setClaimRecipient(userAddress, vaultAddress);
```
##### Frontend Integration
Show users their growing balance in real-time:
```javascript
// Get user's current balance plus unclaimed yield
const balanceWithYield = await yieldUSD.balanceWithYieldOf(userAddress);
// Get just the unclaimed yield amount
const unclaimedYield = await yieldUSD.accruedYieldOf(userAddress);
// Get current yield rate (after protocol fee)
const earnerRate = await yieldUSD.earnerRate();
// Get current index for advanced calculations
const currentIndex = await yieldUSD.currentIndex();
// Check total protocol fees available
const totalFees = await yieldUSD.totalAccruedFee();
// Get latest stored index and rate
const latestIndex = await yieldUSD.latestIndex();
const latestRate = await yieldUSD.latestRate();
const latestTimestamp = await yieldUSD.latestUpdateTimestamp();
// Check if earning is enabled
const isEarningEnabled = await yieldUSD.isEarningEnabled();
// Check claim recipient for a user
const claimRecipient = await yieldUSD.claimRecipientFor(userAddress);
```
##### User Experience Best Practices
1. **Auto-Update Balances**: Use `balanceWithYieldOf()` to show users their real-time balance including accrued yield.
2. **Yield Claiming UI**: Provide a clear "Claim Yield" button that calls `claimYieldFor(userAddress)`.
3. **Yield History**: Track `YieldClaimed` events to show users their yield claiming history.
4. **APY Display**: Calculate and display the current APY based on the `earnerRate()`.
#### 9. Monitoring & Maintenance
##### Key Metrics to Track
* **Total Accrued Yield**: How much yield is available for all users
* **Total Protocol Fees**: Revenue generated from the fee mechanism
* **User Engagement**: How often users are claiming their yield
* **Yield Rate**: Current effective rate after protocol fees
* **Index Growth**: Track how the index grows over time
##### Operational Considerations
* Track gas costs for yield claiming operations.
* Consider implementing automated yield claiming for small amounts.
* Monitor the fee recipient balance for protocol revenue.
* Set up alerts for when earning is disabled.
* **Implement a regular index update strategy (see below).**
##### Index Update Strategy
For users to see their balances grow and for the onchain state to accurately reflect the latest yield, the contract's
index needs to be updated periodically. This is done by calling the public `updateIndex()` function, which checkpoints
the current yield rate and index value into the contract's storage. This function can be called by any account.
While user transactions like `wrap` or `claim` will trigger an update, it is a **best practice not to rely on user
activity**. Instead, you should set up an automated process to call `updateIndex()` on a regular schedule.
**Suggested Strategies:**
* **Backend Cron Job:** The most common approach is to run a simple, time-based script from your backend (e.g., using a
cron job) that calls the function.
* **Keeper Network:** Use a decentralized automation service like Gelato or Chainlink Keepers to execute the call
reliably.
The optimal frequency depends on your application's needs. Common schedules range from once per hour to once per day.
This ensures the onchain index never becomes too stale and provides a smooth experience for your users.
You can monitor the health of your update mechanism by comparing the live index with the last stored index:
```jsx
// Monitor the "drift" between the live index and the last stored index.
// A large difference may indicate your automated update job is not running.
const currentIndex = await yieldUSD.currentIndex(); // Live, calculated value
const latestStoredIndex = await yieldUSD.latestIndex(); // Last value saved onchain
console.log(`Current Index: ${currentIndex}`);
console.log(`Last Stored Index onchain: ${latestStoredIndex}`);
```
**Congratulations!** You now have a fully functional yield-bearing stablecoin that automatically distributes yield to
your users while generating protocol revenue through configurable fees.
### SVM Implementation
Implementation guide for the ScaledUi model on Solana and SVM chains coming soon.
import ZoomableImage from '@/components/ZoomableImage';
## User Yield Model
**Core Concept:** Yield is automatically distributed to all token holders, creating a yield-bearing stablecoin. An
optional protocol fee can be configured to capture a percentage for the admin.
**When to Use:**
* DeFi protocols offering yield-bearing stablecoins to users
* Consumer wallets with built-in savings accounts
* GameFi economies where users are rewarded for holding
* Applications prioritizing user incentives while optionally capturing protocol revenue
### EVM Implementation: MYieldFee
The `MYieldFee` extension is the ideal model for creating a **user-facing, yield-bearing stablecoin**. It allows you to
build a custom token where yield continuously accrues to all holders, while giving you, the protocol developer, the
ability to take a small, configurable fee on that yield.
This model is perfect for dApps, wallets, and DeFi protocols that want to offer a compelling, native stablecoin that
shares the underlying yield with its users, creating a powerful incentive for adoption and liquidity.
**Source Code:**
* L1 (Ethereum):
[`MYieldFee.sol`](https://github.com/m0-foundation/m-extensions/blob/main/src/projects/yieldToAllWithFee/MYieldFee.sol)
* L2 (Spoke Chains):
[`MSpokeYieldFee.sol`](https://github.com/m0-foundation/m-extensions/blob/main/src/projects/yieldToAllWithFee/MSpokeYieldFee.sol)
#### Architecture and Mechanism
`MYieldFee` uses a sophisticated continuous indexing mechanism to distribute yield fairly and efficiently to all token
holders.
##### Roles (Access Control)
`MYieldFee` defines three key management roles:
* **`DEFAULT_ADMIN_ROLE`**: The root administrator, capable of granting and revoking other roles.
* **`FEE_MANAGER_ROLE`**: Controls the protocol's revenue. This role can set the `feeRate` (0-10,000 bps) and the
`feeRecipient` address.
* **`CLAIM_RECIPIENT_MANAGER_ROLE`**: Can redirect a user's yield claims to another address via `setClaimRecipient()`.
Useful for features like auto-compounding vaults or delegating rewards.
##### How It Works: Continuous Indexing
1. **Principal vs. Balance:** When a user wraps, their token `balance` is recorded, along with a `principal` value.
Think of `principal` as their "share" of the total pool.
2. **The Growing Index:** The contract maintains a `currentIndex` which starts at `1e12` (EXP\_SCALED\_ONE) and grows over
time based on the yield rate (minus the protocol fee).
3. **Accruing Yield:** A user's "true" balance at any time is calculated using
`IndexingMath.getPresentAmountRoundedDown(principal, currentIndex)`. Since the index is always increasing, their
effective balance grows continuously. The `accruedYieldOf(account)` is the difference between this true balance and
their last claimed `balance`.
4. **Claiming:** When a user calls `claimYieldFor(account)`, their `balance` is updated to the current true value,
effectively "cashing in" their accrued yield.
5. **The Fee:** The protocol fee is what's left over. The `totalAccruedFee()` is calculated as the contract's total
balance minus the projected total supply owed to all users. This surplus can be claimed by anyone via `claimFee()`,
which mints new tokens to the `feeRecipient`.
##### L1 vs. L2 (`MSpokeYieldFee`)
The mechanism has a slight variation for L2 deployments.
* **On L1 (Ethereum):** `MYieldFee` uses `block.timestamp` to calculate the `currentIndex`, as yield accrues
continuously.
* **On L2s (`MSpokeYieldFee`):** To prevent over-printing tokens due to the delay in index propagation from L1,
`MSpokeYieldFee` uses a `RateOracle`. The index growth is capped by the `latestUpdateTimestamp` from the bridged
index on the L2, ensuring its growth never outpaces the underlying asset.
#### Key Interface & Functions
##### Storage Layout
The state management is more complex to handle the indexing logic.
```solidity
struct MYieldFeeStorageStruct {
uint256 totalSupply;
uint112 totalPrincipal;
uint128 latestIndex;
uint16 feeRate;
address feeRecipient;
uint40 latestUpdateTimestamp;
uint32 latestRate;
bool isEarningEnabled;
mapping(address account => uint256 balance) balanceOf;
mapping(address account => uint112 principal) principalOf;
mapping(address account => address claimRecipient) claimRecipients;
}
```
##### Core Yield Functions
* **`accruedYieldOf(address account) external view`**: Returns the amount of unclaimed yield for a specific user.
Calculated as the difference between their present amount (based on principal and current index) and their recorded
balance.
* **`claimYieldFor(address account) external`**: Claims the accrued yield for a user, updating their balance. Can be
called by anyone on behalf of the user. If a claim recipient is set, the yield is automatically transferred to that
address. Emits `YieldClaimed` and `Transfer` events.
* **`totalAccruedFee() external view`**: Shows the total fee amount available for the protocol to claim. Calculated as
the contract's balance minus the projected total supply.
* **`claimFee() external`**: Mints the accrued fee amount and sends it to the `feeRecipient`. Returns the amount
claimed. Can be called by anyone.
##### Indexing Functions
These functions are the heart of the yield distribution mechanism.
* **`currentIndex() external view`**: Calculates the current, up-to-the-second index value using continuous indexing
math. This is the core of the yield calculation.
* **`earnerRate() external view`**: Returns the effective yield rate for token holders, which is the underlying rate
minus the protocol's `feeRate`: `(ONE_HUNDRED_PERCENT - feeRate) * _currentEarnerRate() / ONE_HUNDRED_PERCENT`.
* **`updateIndex() external`**: Function that updates the index by refreshing the `latestIndex`, `latestRate`, and
`latestUpdateTimestamp` in storage. This is called automatically during key operations and returns the updated index.
##### Management Functions
* **`setFeeRate(uint16 feeRate_) external`**: Callable by the `FEE_MANAGER_ROLE` to adjust the protocol's take rate (in
basis points, 0-10,000). Updates the index if earning is enabled.
* **`setFeeRecipient(address feeRecipient_) external`**: Callable by the `FEE_MANAGER_ROLE` to change the treasury
address. Claims any existing fees for the previous recipient first.
* **`setClaimRecipient(address account, address claimRecipient) external`**: Callable by the
`CLAIM_RECIPIENT_MANAGER_ROLE` to redirect yield claims. Claims any existing yield for the account before making the
change.
##### Earning Control Functions
* **`enableEarning() external`**: Starts earning yield. Updates the index and enables earning. Reverts if earning is
already enabled.
* **`disableEarning() external`**: Stops earning yield. Updates the index and resets earning state. Reverts if earning
is already disabled.
##### View Functions
* **`balanceWithYieldOf(address account) external view`**: Returns the user's current balance plus any unclaimed yield.
* **`principalOf(address account) external view`**: Returns the user's principal amount.
* **`projectedTotalSupply() external view`**: Shows what the total supply would be if all yield was claimed.
* **`totalAccruedYield() external view`**: Shows the total unclaimed yield for all users.
* **`totalPrincipal() external view`**: Returns the total principal of all users.
* **`claimRecipientFor(address account) external view`**: Returns the claim recipient for an account (defaults to the
account itself if none set).
* **`feeRate() external view`**: Returns the current fee rate in basis points.
* **`feeRecipient() external view`**: Returns the current fee recipient address.
* **`isEarningEnabled() external view`**: Returns whether earning is currently enabled.
* **`latestIndex() external view`**: Returns the last stored index.
* **`latestRate() external view`**: Returns the last stored rate.
* **`latestUpdateTimestamp() external view`**: Returns the timestamp of the last index update.
##### Constants
* **`ONE_HUNDRED_PERCENT`**: Set to 10,000 (representing 100% in basis points)
* **`FEE_MANAGER_ROLE`**: `keccak256("FEE_MANAGER_ROLE")`
* **`CLAIM_RECIPIENT_MANAGER_ROLE`**: `keccak256("CLAIM_RECIPIENT_MANAGER_ROLE")`
##### MSpokeYieldFee Specific
For L2 deployments, `MSpokeYieldFee` adds:
* **`rateOracle() external view`**: Returns the address of the rate oracle used to get the earner rate.
**Ready to build?**
[**Follow the implementation guide to deploy your MYieldFee extension.**](/build/models/user-yield/guide/)
### SVM Implementation: ScaledUi
The `ScaledUi` extension is the most seamless way to create a **yield-bearing stablecoin for end-users** on Solana. It
uses the **scaled-ui mechanism** to automatically and efficiently distribute yield to all token holders.
This model is perfect for DeFi protocols, consumer wallets, and any application that wants to offer a compelling
stablecoin where user balances grow automatically in their wallets without requiring any transactions or claims.
**Source Code:** The `ScaledUi` model is compiled from the `m_ext` program using the `scaled-ui` Rust feature flag. The
source code is available in the [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions) repository.
#### Architecture and Mechanism
The `ScaledUi` model combines yield-generating capabilities with the scaled-ui mechanism to create a truly native,
yield-bearing asset. The scaled-ui variant can have an optional fee that goes to the admin.
##### Roles (Access Control)
* **`admin`**: The root administrator, responsible for initializing the contract and managing `wrap_authorities`.
* **`wrap_authority`**: An address authorized by the `admin` to perform wrap and unwrap operations, typically a trusted
swap program.
* **Keeper/Anyone**: The core `sync` function is **permissionless**, allowing any user or automated keeper bot to call
it to keep the yield index up to date.
##### How It Works: Onchain Indexing & Scaled-UI
1. **Initialization:** The extension is deployed with the scaled-ui mechanism enabled.
2. **Wrapping:** Users wrap via an authorized `wrap_authority` and receive the extension token. The underlying balance
is held in the contract's vault (`m_vault`). Extensions automatically sync on wrap/unwrap.
3. **Yield Accrual:** The `m_vault` is registered as an earner in the base $M token program, and its balance begins to
grow as yield accrues.
4. **Permissionless Sync:** Anyone can call the `sync()` instruction, or bridge an index update. This is the engine of
the yield distribution.
5. **Index Calculation:** The `sync()` function reads the current index from the base $M token `earn` program and
compares it to the last synced index stored in the extension's `global_account`. It calculates the yield accrued in
the vault during that period.
6. **Mint Update:** Using this new yield data, the program calculates the new effective scaled-ui rate for the extension
token.
7. **Automatic Balance Updates:** Wallets and explorers will display a higher balance for all holders, reflecting the
accrued yield based on the scaled-ui mechanism. The user's "principal" balance (`amount`) doesn't change, but their
"UI amount" grows with each sync.
8. **Optional Fee:** If configured, a fee can be charged that goes to the admin.
This mechanism is incredibly efficient, as a single `sync` transaction updates the yield for every token holder
simultaneously.
#### Key Interface & Functions
The model's logic is powered by the generic $M Extension program, configured to operate in a rebasing mode.
##### Storage Layout
The `ExtGlobal` account stores the configuration and state, with `YieldConfig` being crucial for tracking the index
synchronization.
```rust
// State structure from m_ext program
pub struct ExtGlobal {
pub admin: Pubkey,
pub ext_mint: Pubkey, // The extension mint using scaled-ui
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,
}
pub struct YieldConfig {
pub variant: YieldVariant, // Set to YieldVariant::ScaledUi
pub last_m_index: u64, // Tracks the last synced index from the base $M program
pub last_ext_index: u64, // Tracks the extension's own index
}
```
##### Core Yield Function
* **`sync()`**: A **permissionless** instruction that updates the yield index. It reads the state of the base $M token
`earn` program and the extension's `m_vault` balance. It then calculates the new scaled-ui rate. The `ext_mint`
account is writable in this instruction, which is essential for this mechanism to work. Index updates can also be
bridged, and extensions automatically sync on wrap/unwrap operations.
##### Management Functions
* **`initialize(wrap_authorities: Vec)`**: Sets up the extension's global state with the ScaledUi 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.
* **`set_fee(fee_bps: u64)`**: Set optional fee in basis points (goes to admin, typically 0 for full pass-through).
##### Standard $M Extension Functions
* **`wrap(amount: u64)`**: Wraps into the extension token. Callable by a whitelisted `wrap_authority`.
* **`unwrap(amount: u64)`**: Unwraps the extension token. The amount specified is the principal amount; the user
receives the principal plus any accrued yield. Callable by a whitelisted `wrap_authority`.
##### Error Handling
* `Unauthorized`: Thrown if a non-authorized address attempts a privileged action.
* `InvalidMint`: Ensures the correct mints are being used in `wrap`/`unwrap`.
* `MathOverflow` / `MathUnderflow`: Protects against errors during the complex index calculations in the `sync`
function.
**Ready to build?**
[**Follow the implementation guide to deploy your ScaledUi extension.**](/build/models/user-yield/overview/)
## Implementing the Treasury Model
This guide provides step-by-step instructions for deploying a Treasury Model stablecoin, where 100% of the yield is
captured by the protocol administrator.
If you haven't already, please review the [**Treasury Model Deep Dive**](/build/models/treasury/guide/) for conceptual
details about this model and its implementations.
:::info[Multi-Collateral Option Available]
This guide covers the standard `MYieldToOne` implementation using `$M` as the only collateral. If you need to accept additional stablecoins (USDC, USDT, DAI), see the [**JMI Implementation Guide**](/build/models/treasury/jmi/guide/) instead.
:::
### EVM Implementation: MYieldToOne
This section covers deploying the `MYieldToOne` contract on EVM chains (Ethereum, Base, Arbitrum, etc.).
#### 1. Setup Your Environment
First, you'll need to clone the `m-extensions` repository and set up your development environment:
1. **Clone the repository:**
```bash
git clone https://github.com/m0-foundation/m-extensions.git
cd m-extensions
```
2. **Follow the setup instructions:** Refer to the
[**README.md**](https://github.com/m0-foundation/m-extensions/blob/main/README.md) and follow all the commands
provided to install dependencies and configure your development environment.
3. **Configure Environment Variables:** Create a `.env` file in the root directory:
```bash
# Private key for deployment (include 0x prefix)
PRIVATE_KEY=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
# RPC URLs
MAINNET_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/your-api-key
SEPOLIA_RPC_URL=https://eth-sepolia.alchemyapi.io/v2/your-api-key
# Etherscan API key for verification
ETHERSCAN_API_KEY=your-etherscan-api-key
```
**Security Warning:** Never commit your `.env` file to version control. Add it to your `.gitignore`:
```bash
echo ".env" >> .gitignore
```
#### 2. Create and Initialize Your Contract
Your custom extension contract will inherit from `MYieldToOne`. The most important step is the `initialize` function,
where you configure your token's name, symbol, and access control roles.
Create a new file in the `src/` directory, for example, `MyTreasuryToken.sol`.
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { MYieldToOne } from "./projects/yieldToOne/MYieldToOne.sol";
contract MyTreasuryToken is MYieldToOne {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address mToken_, address swapFacility_) MYieldToOne(mToken_, swapFacility_) {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol,
address yieldRecipient_,
address admin,
address freezeManager,
address yieldRecipientManager
) public initializer {
MYieldToOne.initialize(
name, // "My Treasury USD"
symbol, // "tUSD"
yieldRecipient_, // Treasury wallet address
admin, // Admin multisig address
freezeManager, // Freeze manager address (can be same as admin)
yieldRecipientManager // Yield recipient manager address
);
}
}
```
##### Key Parameters Explained:
* **`name`**: The full name of your token (e.g., "My Treasury USD")
* **`symbol`**: The token symbol (e.g., "tUSD")
* **`yieldRecipient_`**: The wallet that will receive all yield (your treasury)
* **`admin`**: Address with `DEFAULT_ADMIN_ROLE` (should be a multisig)
* **`freezeManager`**: Address with `FREEZE_MANAGER_ROLE`
* **`yieldRecipientManager`**: Address with `YIELD_RECIPIENT_MANAGER_ROLE`
**Note:** The `mToken` and `swapFacility` addresses are set in the constructor as immutable variables, not in the
initializer.
#### 3. Write Deployment Script
Create a deployment script in the `script/` directory:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Script } from "forge-std/Script.sol";
import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol";
import { MyTreasuryToken } from "../src/MyTreasuryToken.sol";
contract DeployMyTreasuryToken is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
// Deploying a Transparent Proxy, which requires a proxy admin.
address proxy = Upgrades.deployTransparentProxy(
"MyTreasuryToken.sol", // Contract file name
deployer, // The admin for the proxy contract itself
abi.encodeCall( // The initializer call data
MyTreasuryToken.initialize,
(
"My Treasury USD", // name
"tUSD", // symbol
0x..., // yieldRecipient
0x..., // admin (DEFAULT_ADMIN_ROLE for the logic)
0x..., // freezeManager
0x... // yieldRecipientManager
)
)
);
vm.stopBroadcast();
console.log("MyTreasuryToken deployed at:", proxy);
}
}
```
#### 4. Testing Your Contract
Before deployment, create comprehensive tests:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Test } from "forge-std/Test.sol";
import { MyTreasuryToken } from "../src/MyTreasuryToken.sol";
contract MyTreasuryTokenTest is Test {
MyTreasuryToken token;
address admin = makeAddr("admin");
address treasury = makeAddr("treasury");
address user = makeAddr("user");
function setUp() public {
token = new MyTreasuryToken(
address(mockMToken), // $M Token address
address(mockSwapFacility) // SwapFacility address
);
token.initialize(
"My Treasury USD",
"tUSD",
treasury, // Yield recipient
admin, // Admin
admin, // Freeze manager
admin // Yield recipient manager
);
}
function testYieldClaim() public {
// Test yield claiming functionality
// Mock some yield accrual
// Call claimYield()
// Assert yield goes to treasury
}
function testFreezing() public {
// Test freezing functionality
vm.prank(admin);
token.freeze(user);
assertTrue(token.isFrozen(user));
}
function testYieldRecipientChange() public {
// Test changing yield recipient
address newTreasury = makeAddr("newTreasury");
vm.prank(admin);
token.setYieldRecipient(newTreasury);
assertEq(token.yieldRecipient(), newTreasury);
}
}
```
#### 5. Gain M0 Earner Approval
For your contract to accrue yield, its deployed address must be approved as an M0 Earner. This is a crucial governance
step.
* **Calculate Your Address:** Determine your contract's final deployment address. You can do this deterministically
using `CREATE2` or by deploying it to a testnet first.
* **Submit a Proposal:** Create and submit a proposal to M0 Governance to add your contract's address to the `earners`
list in the `TTGRegistrar`.
For more details on this process, see the [Gaining Earner Approval](/build/gaining-earner-approval/) guide.
#### 6. Security & Audit
Even though you are building on an audited template, any modifications or new logic should be thoroughly tested and
independently audited. Security is paramount.
* Write comprehensive tests for your new contract in the `test/` directory.
* Run static analysis tools like Slither
* Engage with a reputable security firm for a full audit if you've made significant modifications
#### 7. Deploy & Launch
##### Deploy to Testnet First
```bash
# Load environment variables
source .env
# Deploy to Sepolia
forge script script/DeployMyTreasuryToken.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Deploy to Mainnet
```bash
# Load environment variables
source .env
# Deploy to Mainnet
forge script script/DeployMyTreasuryToken.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Alternative: Hardware Wallet Deployment
For production deployments, consider using a hardware wallet:
```bash
forge script script/DeployMyTreasuryToken.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--ledger \
--broadcast
```
##### Enable Earning
After your contract is deployed and governance approval is confirmed, call the `enableEarning()` function on your
deployed contract:
```solidity
// Call this function to start yield accrual
myTreasuryToken.enableEarning();
```
This will start the yield accrual process by calling `startEarning()` on the underlying $M Token.
#### 8. Integration & Usage
##### For Treasury Management
1. **Claiming Yield:** Anyone can call the yield claim function when surplus exists:
```solidity
uint256 yieldAmount = myTreasuryToken.claimYield();
```
2. **Managing Yield Recipient:** The yield recipient manager can update where yield goes:
```solidity
// Only YIELD_RECIPIENT_MANAGER_ROLE can call this
myTreasuryToken.setYieldRecipient(newTreasuryAddress);
```
3. **Freezing (if needed):**
```solidity
// Only FREEZE_MANAGER_ROLE can call this
myTreasuryToken.freeze(suspiciousAddress);
myTreasuryToken.unfreeze(rehabilitatedAddress);
// Batch operations
address[] memory accounts = new address[](3);
accounts[0] = addr1;
accounts[1] = addr2;
accounts[2] = addr3;
myTreasuryToken.freezeAccounts(accounts);
```
##### Frontend Integration
Display real-time yield information to your users:
```javascript
// Get current claimable yield
const currentYield = await myTreasuryToken.yield();
// Get total supply
const totalSupply = await myTreasuryToken.totalSupply();
// Get user balance
const userBalance = await myTreasuryToken.balanceOf(userAddress);
// Check if address is frozen
const isFrozen = await myTreasuryToken.isFrozen(userAddress);
// Get yield recipient
const yieldRecipient = await myTreasuryToken.yieldRecipient();
```
#### 9. Monitoring & Maintenance
##### Key Metrics to Track
* **Total Supply**: How much of your token is in circulation
* **Yield Rate**: Current yield being generated
* **Treasury Balance**: Amount accumulated in the yield recipient address
* **Underlying Balance**: Total value held by your contract
##### Operational Considerations
* Monitor the `yield()` function regularly to see claimable amounts
* Set up automated yield claiming if desired
* Keep track of frozen addresses for compliance
* Monitor gas costs for user operations
**Congratulations!** You now have a fully functional treasury-focused stablecoin that automatically accumulates yield
for your protocol while providing users with a stable, 1:1 backed token experience.
### SVM Implementation
Implementation guide for the NoYield model on Solana and SVM chains coming soon.
## 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 →**](/build/models/treasury/jmi/overview/)
***
### 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`](https://github.com/m0-foundation/m-extensions/blob/main/src/projects/yieldToOne/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 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 Yield:** Your deployed `MYieldToOne` contract must call `enableEarning()` to start earning yield. It
begins to accrue yield from the underlying eligible collateral.
3. **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()` 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.
```solidity
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 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
* **`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 with MYieldToOne?** [**Follow the implementation guide →**](/build/models/treasury/guide/)
**Need multi-collateral support?** [**See the JMI implementation guide →**](/build/models/treasury/jmi/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](https://github.com/m0-foundation/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 and receives an equal amount of the new extension token.
The `NoYield` contract's vault (`m_vault`) now holds the underlying balance.
2. **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.
3. **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 `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"`.
```rust
// 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,
}
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)`**: 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 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.**](/build/models/treasury/guide/)
## Implementing the JMI Model
:::info[EVM Only]
The JMI extension is currently available on EVM chains only (Ethereum, Base, Arbitrum, etc.).
:::
This guide provides step-by-step instructions for deploying a JMI (Just Mint It) extension, where multiple collateral
types are accepted and 100% of the yield is captured by a designated recipient.
If you haven't already, please review the [**JMI Deep Dive**](/build/models/treasury/jmi/overview/) for conceptual
details about this model.
:::info[Standard Treasury Model]
For single-collateral deployments, consider the simpler
[**MYieldToOne implementation**](/build/models/treasury/guide/) instead.
:::
### EVM Implementation
This section covers deploying the `JMIExtension` contract on EVM chains (Ethereum, Base, Arbitrum, etc.).
#### 1. Setup Your Environment
First, clone the `m-extensions` repository and set up your development environment:
1. **Clone the repository:**
```bash
git clone https://github.com/m0-foundation/m-extensions.git
cd m-extensions
```
2. **Follow the setup instructions:** Refer to the
[**README.md**](https://github.com/m0-foundation/m-extensions/blob/main/README.md) and follow all commands to install
dependencies and configure your development environment.
3. **Configure Environment Variables:** Create a `.env` file in the root directory:
```bash
# Private key for deployment (include 0x prefix)
PRIVATE_KEY=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
# RPC URLs
MAINNET_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/your-api-key
SEPOLIA_RPC_URL=https://eth-sepolia.alchemyapi.io/v2/your-api-key
# Etherscan API key for verification
ETHERSCAN_API_KEY=your-etherscan-api-key
```
**Security Warning:** Never commit your `.env` file to version control:
```bash
echo ".env" >> .gitignore
```
#### 2. Create Your Contract
Your custom JMI extension will inherit from `JMIExtension`. Create a new file in the `src/` directory, for example,
`MyJMIToken.sol`:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { JMIExtension } from "./projects/jmi/JMIExtension.sol";
contract MyJMIToken is JMIExtension {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address mToken_, address swapFacility_) JMIExtension(mToken_, swapFacility_) {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol,
address yieldRecipient_,
address admin,
address assetCapManager,
address freezeManager,
address pauser,
address yieldRecipientManager
) public override initializer {
JMIExtension.initialize(
name, // "My JMI USD"
symbol, // "jUSD"
yieldRecipient_, // Treasury wallet address
admin, // Admin multisig address
assetCapManager, // Asset cap manager address
freezeManager, // Freeze manager address
pauser, // Pauser address
yieldRecipientManager // Yield recipient manager address
);
}
}
```
##### Key Parameters Explained
* **`name`**: The full name of your token (e.g., "My JMI USD")
* **`symbol`**: The token symbol (e.g., "jUSD")
* **`yieldRecipient_`**: The wallet receiving all yield (your treasury)
* **`admin`**: Address with `DEFAULT_ADMIN_ROLE` (should be a multisig)
* **`assetCapManager`**: Address with `ASSET_CAP_MANAGER_ROLE` (manages collateral caps)
* **`freezeManager`**: Address with `FREEZE_MANAGER_ROLE` (can freeze addresses)
* **`pauser`**: Address with `PAUSER_ROLE` (emergency stop)
* **`yieldRecipientManager`**: Address with `YIELD_RECIPIENT_MANAGER_ROLE`
**Note:** The `mToken` and `swapFacility` addresses are set in the constructor as immutable variables.
#### 3. Write Deployment Script
Create a deployment script in the `script/` directory:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Script, console } from "forge-std/Script.sol";
import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol";
import { MyJMIToken } from "../src/MyJMIToken.sol";
contract DeployMyJMIToken is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
// Configuration - update these addresses for your deployment
address mToken = 0x...; // M Token address for your chain
address swapFacility = 0x...; // SwapFacility address for your chain
vm.startBroadcast(deployerPrivateKey);
address proxy = Upgrades.deployTransparentProxy(
"MyJMIToken.sol",
deployer,
abi.encodeCall(
MyJMIToken.initialize,
(
"My JMI USD", // name
"jUSD", // symbol
0x..., // yieldRecipient
0x..., // admin
0x..., // assetCapManager
0x..., // freezeManager
0x..., // pauser
0x... // yieldRecipientManager
)
),
Upgrades.DeployOptions({
constructorData: abi.encode(mToken, swapFacility)
})
);
vm.stopBroadcast();
console.log("MyJMIToken deployed at:", proxy);
}
}
```
#### 4. Testing Your Contract
Create comprehensive tests before deployment:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Test } from "forge-std/Test.sol";
import { MyJMIToken } from "../src/MyJMIToken.sol";
import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol";
contract MyJMITokenTest is Test {
MyJMIToken token;
address admin = makeAddr("admin");
address assetCapManager = makeAddr("assetCapManager");
address treasury = makeAddr("treasury");
address user = makeAddr("user");
address mockUSDC;
address mockDAI;
function setUp() public {
// Deploy mock tokens
// Deploy your JMI token
// Initialize with test addresses
}
function testSetAssetCap() public {
uint256 usdcCap = 1_000_000e6; // 1M USDC
vm.prank(assetCapManager);
token.setAssetCap(mockUSDC, usdcCap);
assertEq(token.assetCap(mockUSDC), usdcCap);
assertTrue(token.isAllowedAsset(mockUSDC));
}
function testWrapWithUSDC() public {
// Setup: Set cap and mint USDC to swap facility
uint256 amount = 1000e6;
// Test wrapping USDC through SwapFacility
// Assert JMI tokens minted
// Assert totalAssets increased
}
function testUnwrapLimitedToMBacking() public {
// Setup: Wrap some M and some USDC
// Try to unwrap more than M backing
// Expect revert with InsufficientMBacking
}
function testReplaceAssetWithM() public {
// Setup: Have USDC in the contract
// Call replaceAssetWithM to swap M for USDC
// Assert totalAssets decreased
// Assert USDC sent to recipient
}
function testFreezing() public {
vm.prank(admin); // Assuming admin has freeze manager role
token.freeze(user);
assertTrue(token.isFrozen(user));
}
function testYieldClaim() public {
// Setup: Wrap M and let yield accrue
// Call claimYield
// Assert yield went to treasury
}
}
```
#### 5. Configure Supported Collateral
After deployment, the asset cap manager must configure which collateral assets are accepted:
```solidity
// Set caps for each supported stablecoin
// Cap is in the asset's native decimals
// USDC (6 decimals) - 10M cap
myJMIToken.setAssetCap(USDC_ADDRESS, 10_000_000e6);
// DAI (18 decimals) - 5M cap
myJMIToken.setAssetCap(DAI_ADDRESS, 5_000_000e18);
// USDT (6 decimals) - 5M cap
myJMIToken.setAssetCap(USDT_ADDRESS, 5_000_000e6);
```
**Important:** Only set caps for stablecoins that maintain a reliable 1:1 peg to the dollar. The JMI model assumes 1:1
peg for all accepted collateral.
#### 6. Gain M0 Earner Approval
For your contract to accrue yield on the `$M` it holds, its deployed address must be approved as an M0 Earner.
* **Calculate Your Address:** Determine your contract's final deployment address using `CREATE2` or by deploying to a
testnet first.
* **Submit a Proposal:** Create and submit a proposal to M0 Governance to add your contract's address to the `earners`
list in the `TTGRegistrar`.
For more details, see the [Gaining Earner Approval](/build/gaining-earner-approval/) guide.
#### 7. Security & Audit
Even though you are building on an audited template, any modifications should be thoroughly tested and independently
audited:
* Write comprehensive tests covering all scenarios
* Run static analysis tools like Slither
* Engage a reputable security firm for a full audit if you've made significant modifications
#### 8. Deploy & Launch
##### Deploy to Testnet First
```bash
source .env
forge script script/DeployMyJMIToken.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Deploy to Mainnet
```bash
source .env
forge script script/DeployMyJMIToken.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Hardware Wallet Deployment
For production deployments:
```bash
forge script script/DeployMyJMIToken.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--ledger \
--broadcast
```
##### Enable Earning
After deployment and governance approval:
```solidity
myJMIToken.enableEarning();
```
#### 9. Integration & Usage
##### Managing Asset Caps
```solidity
// Check current cap
uint256 currentCap = myJMIToken.assetCap(USDC_ADDRESS);
// Update cap (only ASSET_CAP_MANAGER_ROLE)
myJMIToken.setAssetCap(USDC_ADDRESS, newCap);
// Disable an asset (set cap to 0)
myJMIToken.setAssetCap(USDC_ADDRESS, 0);
```
##### Checking Backing Status
```solidity
// Total JMI supply
uint256 totalSupply = myJMIToken.totalSupply();
// Non-M asset backing
uint256 totalAssets = myJMIToken.totalAssets();
// M backing (what can be unwrapped)
uint256 mBacking = totalSupply - totalAssets;
// Specific asset backing
uint256 usdcBacking = myJMIToken.assetBalanceOf(USDC_ADDRESS);
```
##### Claiming Yield
```solidity
// Check claimable yield
uint256 pendingYield = myJMIToken.yield();
// Claim yield (anyone can call, goes to yieldRecipient)
uint256 claimedAmount = myJMIToken.claimYield();
```
##### Managing Yield Recipient
```solidity
// Only YIELD_RECIPIENT_MANAGER_ROLE
myJMIToken.setYieldRecipient(newTreasuryAddress);
```
##### Freezing Addresses
```solidity
// Only FREEZE_MANAGER_ROLE
myJMIToken.freeze(suspiciousAddress);
myJMIToken.unfreeze(clearedAddress);
// Batch operations
address[] memory toFreeze = new address[](3);
toFreeze[0] = addr1;
toFreeze[1] = addr2;
toFreeze[2] = addr3;
myJMIToken.freezeAccounts(toFreeze);
```
##### Emergency Pause
```solidity
// Only PAUSER_ROLE
myJMIToken.pause(); // Halts wrap, unwrap, transfer, replaceAssetWithM
myJMIToken.unpause(); // Resumes operations
```
##### Frontend Integration
```javascript
// Get backing breakdown
const totalSupply = await myJMIToken.totalSupply();
const totalAssets = await myJMIToken.totalAssets();
const mBacking = totalSupply - totalAssets;
// Check specific asset backing
const usdcBacking = await myJMIToken.assetBalanceOf(USDC_ADDRESS);
const daisBacking = await myJMIToken.assetBalanceOf(DAI_ADDRESS);
// Check if wrap is allowed
const canWrap = await myJMIToken.isAllowedToWrap(USDC_ADDRESS, amount);
// Check if unwrap is allowed
const canUnwrap = await myJMIToken.isAllowedToUnwrap(amount);
// Check if asset replacement is allowed (amount in asset decimals)
const canReplace = await myJMIToken.isAllowedToReplaceAssetWithM(USDC_ADDRESS, assetAmount);
// Get yield info
const pendingYield = await myJMIToken.yield();
const yieldRecipient = await myJMIToken.yieldRecipient();
// Check account status
const isFrozen = await myJMIToken.isFrozen(userAddress);
const isPaused = await myJMIToken.paused();
```
#### 10. Monitoring & Maintenance
##### Key Metrics to Track
| Metric | Description |
| ------------------ | ----------------------------------- |
| Total Supply | Total JMI tokens in circulation |
| M Backing | `totalSupply - totalAssets` |
| Total Non-M Assets | Sum of all collateral backings |
| Per-Asset Backing | Individual collateral levels |
| Asset Utilization | `assetBalance / assetCap` per asset |
| Yield Rate | Current yield accrual rate |
| Pending Yield | Unclaimed yield amount |
##### Operational Considerations
1. **Monitor Asset Caps:** Regularly review if caps need adjustment based on:
* Stablecoin peg stability
* Market conditions
* Protocol risk tolerance
2. **Rebalancing:** Use `replaceAssetWithM` to rebalance backing when needed:
* Arbitrageurs can swap `$M` for non-`$M` collateral
* This naturally moves the backing toward `$M` over time
3. **Yield Claiming:** Set up automated yield claiming if desired.
4. **Emergency Procedures:**
* Document pause/unpause procedures
* Have freeze manager ready for suspicious activity
* Maintain secure communication channels for emergencies
5. **Collateral Monitoring:**
* Track peg stability of accepted stablecoins
* Be prepared to reduce/remove caps for depegged assets
**Congratulations!** You now have a fully functional multi-collateral stablecoin that accepts various stablecoins while
automatically accumulating yield for your protocol.
## Multi-Collateral Option: JMIExtension
:::info[EVM Only]
The JMI extension is currently available on EVM chains only (Ethereum, Base, Arbitrum, etc.). SVM support is planned for a future release.
:::
The `JMIExtension` (Just Mint It) is an enhanced Treasury Model implementation that accepts multiple collateral types while still directing 100% of yield to a single recipient.
**When to Use JMI instead of standard MYieldToOne:**
* Projects requiring flexibility to accept multiple stablecoin collateral types (USDC, USDT, DAI)
* Protocols wanting a non-rebasing stablecoin backed by a diversified collateral pool
* Ecosystems that need to manage risk exposure to different stablecoin assets through configurable caps
* Treasury management scenarios where yield is centralized but collateral sources are diversified
**Source Code:** [`JMIExtension.sol`](https://github.com/m0-foundation/m-extensions/blob/main/src/projects/jmi/JMIExtension.sol)
### Key Differentiators from MYieldToOne
| Feature | MYieldToOne (Standard) | JMIExtension (JMI) |
| --------------------- | ---------------------- | ----------------------------------- |
| Collateral | Single-collateral | Multi-collateral (USDC, USDT, etc.) |
| Asset Caps | N/A | Configurable per asset |
| Unwrap Logic | Always backed 1:1 | Limited to base backing portion |
| Additional Operations | None | `replaceAssetWithM` |
### Architecture Overview
`JMIExtension` inherits from `MYieldToOne`, which itself inherits from `MExtension`, `Freezable`, and `Pausable`. This inheritance chain provides:
* **ERC-20 Token Logic** via `MExtension`
* **Yield Distribution** via `MYieldToOne` (100% to single recipient)
* **Compliance Controls** via `Freezable` (address-level restrictions)
* **Emergency Controls** via `Pausable` (contract-wide halt)
* **Multi-Collateral Logic** via `JMIExtension` (asset caps, multiple collateral types)
```
JMIExtension
├── IJMIExtension (interface)
├── JMIExtensionLayout (storage)
└── MYieldToOne
├── IMYieldToOne (interface)
├── MYieldToOneStorageLayout (storage)
├── MExtension (core ERC-20 + wrap/unwrap)
├── Freezable (address freezing)
└── Pausable (emergency stop)
```
### How It Works
1. **Multi-Collateral Wrapping:** Users can wrap either `$M` or any approved stablecoin (USDC, DAI, etc.) through the SwapFacility. The contract tracks each asset's balance separately.
2. **Asset Caps:** Each non-`$M` collateral has a maximum cap. This limits risk exposure to any single stablecoin. The `$M` token has no cap and can always be wrapped.
3. **Yield Accrual:** Only the `$M` portion of the backing earns yield. As the contract's `$M` balance grows beyond its required backing, yield becomes claimable.
4. **Yield Distribution:** The `claimYield()` function mints new JMI tokens to the designated `yieldRecipient`, equal to the yield surplus.
5. **Selective Unwrapping:** Users can only unwrap to `$M`. The maximum unwrap amount is limited to the `$M` backing (total supply minus non-`$M` asset backing).
6. **Asset Replacement:** The `replaceAssetWithM` function allows swapping `$M` for any non-`$M` collateral held by the contract, enabling arbitrageurs to rebalance the backing.
### Backing Model
The total supply of JMI tokens is backed by two components:
```
Total Supply = M Backing + Total Non-M Assets
```
Where:
* **M Backing** = `totalSupply() - totalAssets()`
* **Total Non-M Assets** = Sum of all non-`$M` collateral (in extension decimals)
The claimable yield is calculated as:
```
Yield = mBalanceOf(contract) - M Backing
```
The JMI backing model assumes a 1:1 peg between all deposited assets and `$M`. This means 1 USDC deposited equals 1 JMI token minted, and 1 DAI deposited equals 1 JMI token minted (accounting for decimal conversions).
### Roles (Access Control)
JMIExtension uses OpenZeppelin's `AccessControl` with these roles:
| Role | Permissions |
| ---------------------------------- | ----------------------------------------------------------------------------------------------- |
| **`DEFAULT_ADMIN_ROLE`** | Super-user. Can grant and revoke any role. Should be a secure multi-sig or governance contract. |
| **`ASSET_CAP_MANAGER_ROLE`** | Manages risk by setting caps on each non-`$M` collateral asset via `setAssetCap()`. |
| **`YIELD_RECIPIENT_MANAGER_ROLE`** | Controls where yield is distributed via `setYieldRecipient()`. |
| **`FREEZE_MANAGER_ROLE`** | Can freeze/unfreeze addresses from interacting with the token. |
| **`PAUSER_ROLE`** | Can pause/unpause the entire contract in emergencies. |
### Core Functions
#### Wrapping Functions
| Function | Description |
| -------------------------------- | ----------------------------------------------------------------------------------------- |
| `wrap(asset, recipient, amount)` | Mints JMI tokens by depositing a non-`$M` collateral asset. Called via SwapFacility. |
| `wrap(recipient, amount)` | Mints JMI tokens by depositing `$M` (inherited from MExtension). Called via SwapFacility. |
#### Unwrapping Functions
| Function | Description |
| --------------------------- | -------------------------------------------------------------------------- |
| `unwrap(recipient, amount)` | Burns JMI tokens and sends `$M` to the recipient. Limited to `$M` backing. |
#### Asset Management Functions
| Function | Access | Description |
| --------------------------------------------- | ------------------------ | -------------------------------------------------------- |
| `setAssetCap(asset, cap)` | `ASSET_CAP_MANAGER_ROLE` | Sets the maximum balance allowed for a collateral asset. |
| `replaceAssetWithM(asset, recipient, amount)` | SwapFacility only | Swaps `$M` for non-`$M` collateral held by the contract. |
#### Yield Functions (Inherited)
| Function | Description |
| ---------------------------- | -------------------------------------------------------------- |
| `yield()` | Returns the current claimable yield amount. |
| `claimYield()` | Claims yield by minting new JMI tokens to the yield recipient. |
| `setYieldRecipient(address)` | Updates the yield recipient address. |
#### View Functions
| Function | Returns | Description |
| --------------------------------------------- | --------- | ---------------------------------------------- |
| `assetBalanceOf(asset)` | `uint256` | Tracked balance of a collateral asset |
| `assetCap(asset)` | `uint256` | Cap for a collateral asset (0 = not allowed) |
| `assetDecimals(asset)` | `uint8` | Cached decimals for a collateral asset |
| `totalAssets()` | `uint256` | Total non-`$M` backing (in extension decimals) |
| `isAllowedAsset(asset)` | `bool` | True if asset is `$M` or has non-zero cap |
| `isAllowedToWrap(asset, amount)` | `bool` | True if wrap would succeed |
| `isAllowedToUnwrap(amount)` | `bool` | True if unwrap would succeed |
| `isAllowedToReplaceAssetWithM(asset, amount)` | `bool` | True if asset balance >= amount |
#### Freezing & Pause Functions (Inherited)
| Function | Access | Description |
| ------------------------------ | --------------------- | --------------------------- |
| `freeze(account)` | `FREEZE_MANAGER_ROLE` | Freezes an address |
| `unfreeze(account)` | `FREEZE_MANAGER_ROLE` | Unfreezes an address |
| `freezeAccounts(accounts[])` | `FREEZE_MANAGER_ROLE` | Batch freeze |
| `unfreezeAccounts(accounts[])` | `FREEZE_MANAGER_ROLE` | Batch unfreeze |
| `isFrozen(account)` | Public | Check if address is frozen |
| `pause()` | `PAUSER_ROLE` | Pauses the contract |
| `unpause()` | `PAUSER_ROLE` | Unpauses the contract |
| `paused()` | Public | Check if contract is paused |
### Events
| Event | Parameters | Description |
| -------------------- | ---------------------------------------------- | --------------------------- |
| `AssetCapSet` | `asset`, `cap` | Asset cap was updated |
| `AssetReplacedWithM` | `asset`, `assetAmount`, `recipient`, `mAmount` | Collateral swapped for `$M` |
| `YieldClaimed` | `amount` | Yield was claimed |
| `YieldRecipientSet` | `yieldRecipient` | Yield recipient changed |
| `Transfer` | `from`, `to`, `amount` | ERC-20 transfer |
| `Approval` | `owner`, `spender`, `amount` | ERC-20 approval |
| `Frozen` | `account`, `timestamp` | Account was frozen |
| `Unfrozen` | `account`, `timestamp` | Account was unfrozen |
| `Paused` | `account` | Contract was paused |
| `Unpaused` | `account` | Contract was unpaused |
### Errors
| Error | Description |
| ------------------------------------------------------ | --------------------------------------------------------- |
| `InvalidAsset(asset)` | Asset is `address(0)` or `$M` (for multi-asset functions) |
| `AssetCapReached(asset)` | Deposit would exceed asset cap |
| `InsufficientAssetBacking(asset, required, available)` | Not enough collateral for replacement |
| `InsufficientAssetReceived(asset, expected, received)` | Fee-on-transfer token detected |
| `InsufficientMBacking(required, available)` | Not enough `$M` backing for unwrap |
| `ZeroAssetCapManager()` | Asset cap manager is zero address |
| `ZeroAdmin()` | Admin is zero address |
| `ZeroYieldRecipient()` | Yield recipient is zero address |
| `ZeroYieldRecipientManager()` | Yield recipient manager is zero address |
| `ZeroFreezeManager()` | Freeze manager is zero address |
| `ZeroPauser()` | Pauser is zero address |
| `AccountFrozen(account)` | Operation blocked due to frozen account |
| `EnforcedPause()` | Operation blocked due to paused state |
### Important Constraints
* **Fee-on-Transfer Tokens:** Not supported. The contract reverts if fewer tokens are received than expected.
* **Only 1:1 Pegged Stablecoins:** The model assumes all accepted collateral is pegged 1:1 to the dollar.
* **Unwrap Limited to M Backing:** Users cannot unwrap more JMI than the contract's `$M` backing supports.
### Security Considerations
1. **Fee-on-Transfer Protection:** The contract explicitly checks received amounts match expected amounts, reverting on discrepancies.
2. **Inflation Attack Prevention:** The `replaceAssetWithM` function uses the tracked `assetBalanceOf` rather than actual token balance, preventing manipulation via direct token transfers.
3. **Access Control:** All sensitive functions are protected by role-based access control.
4. **Emergency Controls:** The `PAUSER_ROLE` can halt operations in case of security incidents.
5. **Frozen Accounts:** The `FREEZE_MANAGER_ROLE` can block compromised addresses from interacting with the token.
6. **Upgradability:** The contract uses OpenZeppelin's transparent proxy pattern, allowing for future upgrades with proper governance.
**Ready to build?** [**Follow the JMI implementation guide →**](/build/models/treasury/jmi/guide/)
## Choosing Your M0 Extension Model
M0 provides powerful, pre-built templates to get you to market faster. These models are audited, battle-tested starting
points for the most common use cases. Each one offers a different approach to handling the underlying yield generated by
eligible collateral, giving you the flexibility to build the exact product you need.
### Three Core Models
M0 offers **three distinct extension models**, each available on both **EVM and SVM chains**. While the implementation
details differ between EVM smart contracts and SVM programs, the core yield distribution logic and use cases remain
consistent.
| Model Type | Yield Distribution | Best For | EVM Implementation | SVM Implementation |
| :---------------------- | :------------------------------------------------------------------------ | :----------------------------------------------------------------- | :----------------- | :----------------- |
| **Treasury Model** | 100% of yield captured by protocol admin | Protocol treasuries, ecosystem funds, centralized yield management | `MYieldToOne` | `NoYield` |
| **User Yield Model** | Yield distributed to all token holders (optional admin fee) | DeFi protocols, consumer wallets, yield-bearing stablecoins | `MYieldFee` | `ScaledUi` |
| **Institutional Model** | Permissioned yield distribution with delegated management and custom fees | Institutional platforms, B2B services, prime brokerages | `MEarnerManager` | `Crank` |
### Model 1: 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: `MYieldToOne`
Creates an ERC-20 token backed 1:1 by eligible collateral. All yield is claimed by calling `claimYield()`, which mints
new tokens to a single designated recipient.
* **Yield Claiming:** Anyone can call `claimYield()` to mint accumulated yield to the configured recipient
* **Access Control:** Role-based permissions for managing the recipient address and freezing accounts
* **Source Code:**
[`MYieldToOne.sol`](https://github.com/m0-foundation/m-extensions/blob/main/src/projects/yieldToOne/MYieldToOne.sol)
[**» Deep Dive on MYieldToOne (EVM)**](/build/models/treasury/overview/)
:::info[Multi-Collateral Option]
Need to support multiple collateral types? The **JMIExtension** extends `MYieldToOne` to accept additional stablecoins (USDC, USDT, DAI) with configurable caps while still directing all yield to a single recipient. [**Learn more about JMI →**](/build/models/treasury/jmi/overview/)
:::
#### SVM Implementation: `NoYield`
Creates a token backed 1:1 by eligible collateral. The admin claims all accumulated yield via the `claim_fees`
instruction.
* **Yield Claiming:** Only the admin can call `claim_fees` to mint accumulated yield to a chosen recipient
* **Token Standard:** Uses Token-2022 (or legacy Token Program for compatibility)
* **Source Code:** [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions)
[**» Deep Dive on NoYield (SVM)**](/build/models/treasury/guide/#svm-implementation-noyield)
### Model 2: User Yield Model
**Core Concept:** Yield is automatically distributed to all token holders, creating a yield-bearing stablecoin. An
optional protocol fee can be configured to capture a percentage for the admin.
**When to Use:**
* DeFi protocols offering yield-bearing stablecoins to users
* Consumer wallets with built-in savings accounts
* GameFi economies where users are rewarded for holding
* Applications prioritizing user incentives while optionally capturing protocol revenue
#### EVM Implementation: `MYieldFee`
User balances automatically increase as yield accrues. You can configure a fee (0-100%) to redirect a portion of yield
to your protocol.
* **Automatic Yield:** User balances grow automatically—no claiming required
* **Optional Fee:** Configure what percentage of yield goes to users vs. your protocol treasury
* **Source Code:** [`MYieldFee.sol`](https://github.com/m0-foundation/m-extensions)
[**» Deep Dive on MYieldFee (EVM)**](/build/models/user-yield/guide/#evm-implementation-myieldfee)
#### SVM Implementation: `ScaledUi`
User balances automatically increase as yield accrues. Anyone can call `sync()` to update the yield rate for all holders
at once.
* **Automatic Yield:** User balances grow automatically—no claiming required
* **Permissionless Updates:** Anyone can call `sync()` to update yield (not just admin)
* **Optional Fee:** Support for protocol fee (typically set to 0 for full pass-through)
* **Source Code:** [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions)
[**» Deep Dive on ScaledUi (SVM)**](/build/models/user-yield/guide/#svm-implementation-scaledui)
### Model 3: Institutional Model
**Core Concept:** Three-tier permissioned architecture where an admin delegates authority to Earn Managers, who manage
their own sets of earners with custom fee arrangements.
**When to Use:**
* Institutional platforms serving multiple client organizations
* B2B services requiring bespoke yield-sharing agreements
* Prime brokerages with per-client fee structures
* Platforms needing granular control over who can hold and earn yield
#### EVM Implementation: `MEarnerManager`
Only whitelisted addresses can hold tokens and earn yield. Each earner has a customizable fee rate that determines their
yield share.
* **Permissioned Holding:** Only approved addresses can hold, transfer, or earn yield
* **Custom Fees:** Set individual fee rates for each earner (e.g., VIP client gets 95% yield, standard gets 80%)
* **Source Code:** [`MEarnerManager.sol`](https://github.com/m0-foundation/m-extensions)
[**» Deep Dive on MEarnerManager (EVM)**](/build/models/institutional/guide/#evm-implementation-mearnermanager)
#### SVM Implementation: `Crank`
Three-tier hierarchy: Admin authorizes Earn Managers, who each manage their own set of earners with custom fees. **Used
by Wrapped $M (wM)**.
* **Delegated Management:** Earn Managers can independently manage their earners and set fees
* **Yield Distribution:** Automated bot calculates yield offchain and distributes it onchain
* **Source Code:** [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions)
[**» Deep Dive on Crank (SVM)**](/build/models/institutional/guide/#svm-implementation-crank)
### Choosing Your Path
1. **Choose your model type** based on your yield distribution needs:
* **Treasury Model** if you want centralized yield capture
* **User Yield Model** if you want to share yield with token holders
* **Institutional Model** if you need granular permissioning and custom fees
2. **Choose your chain** based on your ecosystem:
* **EVM chains** for traditional smart contract platforms (Ethereum, Base, etc.)
* **SVM chains** for Solana, Fogo, or other SVM-compatible networks
3. **Review implementation details** by reading the deep-dive documentation for your chosen implementation
Ready to build? Check out our [Implementation Guides](/build/models/treasury/guide/) to get started.
## Implementing the Institutional Model
This guide provides step-by-step instructions for deploying an Institutional Model stablecoin with granular, per-account
control and custom fee structures for institutional clients.
If you haven't already, please review the [**Institutional Model Deep Dive**](/build/models/institutional/guide/) for
conceptual details about this model and its implementations.
### EVM Implementation
This section covers deploying the `MEarnerManager` contract on EVM chains (Ethereum, Base, Arbitrum, etc.).
#### 1. Setup Your Environment
First, you'll need to clone the `m-extensions` repository and set up your development environment:
1. **Clone the repository:**
```bash
git clone https://github.com/m0-foundation/m-extensions.git
cd m-extensions
```
2. **Follow the setup instructions:** Refer to the
[**README.md**](https://github.com/m0-foundation/m-extensions/blob/main/README.md) and follow all the commands
provided to install dependencies and configure your development environment.
3. **Configure Environment Variables:** Create a `.env` file in the root directory:
```bash
# Private key for deployment (include 0x prefix)
PRIVATE_KEY=0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
# RPC URLs
MAINNET_RPC_URL=https://eth-mainnet.alchemyapi.io/v2/your-api-key
SEPOLIA_RPC_URL=https://eth-sepolia.alchemyapi.io/v2/your-api-key
# Etherscan API key for verification
ETHERSCAN_API_KEY=your-etherscan-api-key
```
**Security Warning:** Never commit your `.env` file to version control. Add it to your `.gitignore`:
```bash
echo ".env" >> .gitignore
```
#### 2. Create and Initialize Your Contract
Your custom contract will inherit from `MEarnerManager`. In the `initialize` function, you will set up the core roles
and the initial fee recipient.
Create a new file in the `src/` directory, for example, `InstitutionalUSD.sol`.
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { MEarnerManager } from "./projects/earnerManager/MEarnerManager.sol";
contract InstitutionalUSD is MEarnerManager {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address mToken_, address swapFacility_) MEarnerManager(mToken_, swapFacility_) {
_disableInitializers();
}
function initialize(
string memory name,
string memory symbol,
address admin,
address earnerManager,
address feeRecipient_
) public initializer {
MEarnerManager.initialize(
name, // "Institutional M USD"
symbol, // "iMUSD"
admin, // Admin multisig address
earnerManager, // Platform operator address
feeRecipient_ // Protocol treasury address
);
}
}
```
##### Key Parameters Explained:
* **`name`**: The full name of your token (e.g., "Institutional M USD")
* **`symbol`**: The token symbol (e.g., "iMUSD")
* **`admin`**: Address with `DEFAULT_ADMIN_ROLE` (should be a multisig)
* **`earnerManager`**: Address with `EARNER_MANAGER_ROLE` (your platform operator)
* **`feeRecipient_`**: Address that receives all protocol fees
**Note:** The `mToken` and `swapFacility` addresses are set in the constructor as immutable variables, not in the
initializer.
#### 3. Write Deployment Script
Create a deployment script in the `script/` directory:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Script } from "forge-std/Script.sol";
import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol";
import { InstitutionalUSD } from "../src/InstitutionalUSD.sol";
contract DeployInstitutionalUSD is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
// Deploying a Transparent Proxy, which requires a proxy admin.
address proxy = Upgrades.deployTransparentProxy(
"InstitutionalUSD.sol", // Contract file name
deployer, // The admin for the proxy contract itself
abi.encodeCall( // The initializer call data
InstitutionalUSD.initialize,
(
"Institutional M USD", // name
"iMUSD", // symbol
0x..., // admin (DEFAULT_ADMIN_ROLE for the logic)
0x..., // earnerManager
0x... // feeRecipient
)
)
);
vm.stopBroadcast();
console.log("InstitutionalUSD deployed at:", proxy);
}
}
```
#### 4. Testing Your Contract
Before deployment, create comprehensive tests that focus on the whitelisting logic and fee mechanisms:
```solidity
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.26;
import { Test } from "forge-std/Test.sol";
import { InstitutionalUSD } from "../src/InstitutionalUSD.sol";
contract InstitutionalUSDTest is Test {
InstitutionalUSD token;
address admin = makeAddr("admin");
address earnerManager = makeAddr("earnerManager");
address feeRecipient = makeAddr("feeRecipient");
address client1 = makeAddr("client1");
address client2 = makeAddr("client2");
address unauthorized = makeAddr("unauthorized");
function setUp() public {
token = new InstitutionalUSD(
address(mockMToken), // $M Token address
address(mockSwapFacility) // SwapFacility address
);
token.initialize(
"Institutional M USD",
"iMUSD",
admin, // Admin
earnerManager, // Earner manager
feeRecipient // Fee recipient
);
}
function testWhitelisting() public {
// Test that only EARNER_MANAGER_ROLE can whitelist
vm.prank(earnerManager);
token.setAccountInfo(client1, true, 500); // 5% fee rate
assertTrue(token.isWhitelisted(client1));
assertEq(token.feeRateOf(client1), 500);
}
function testUnauthorizedAccess() public {
// Test that non-whitelisted addresses cannot interact
vm.expectRevert(abi.encodeWithSelector(token.NotWhitelisted.selector, unauthorized));
vm.prank(unauthorized);
// This should fail since unauthorized is not whitelisted
token.transfer(client1, 100);
}
function testYieldClaimWithFees() public {
// Test yield claiming with different fee rates
vm.prank(earnerManager);
token.setAccountInfo(client1, true, 1000); // 10% fee
// Mock yield accrual and test claiming
// Verify correct fee split between client and feeRecipient
}
function testBatchWhitelisting() public {
address[] memory accounts = new address[](2);
bool[] memory statuses = new bool[](2);
uint16[] memory feeRates = new uint16[](2);
accounts[0] = client1;
accounts[1] = client2;
statuses[0] = true;
statuses[1] = true;
feeRates[0] = 500; // 5%
feeRates[1] = 1000; // 10%
vm.prank(earnerManager);
token.setAccountInfo(accounts, statuses, feeRates);
assertTrue(token.isWhitelisted(client1));
assertTrue(token.isWhitelisted(client2));
assertEq(token.feeRateOf(client1), 500);
assertEq(token.feeRateOf(client2), 1000);
}
function testEarningCanOnlyBeEnabledOnce() public {
// Test that earning can only be enabled once
token.enableEarning();
vm.expectRevert(abi.encodeWithSelector(token.EarningCannotBeReenabled.selector));
token.enableEarning();
}
}
```
#### 5. Gain M0 Earner Approval & Audit
Follow the standard procedure for getting your contract ready for mainnet:
* Gain M0 Earner approval for your contract address via governance. See the
[Gaining Earner Approval](/build/gaining-earner-approval/) guide.
* Conduct a thorough security audit. Pay special attention to access control and the whitelisting logic.
#### 6. Deploy and Enable Earning
##### Deploy to Testnet First
```bash
# Load environment variables
source .env
# Deploy to Sepolia
forge script script/DeployInstitutionalUSD.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Deploy to Mainnet
```bash
# Load environment variables
source .env
# Deploy to Mainnet
forge script script/DeployInstitutionalUSD.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--broadcast
```
##### Alternative: Hardware Wallet Deployment
For production deployments, consider using a hardware wallet:
```bash
forge script script/DeployInstitutionalUSD.s.sol \
--rpc-url $MAINNET_RPC_URL \
--verify \
--etherscan-api-key $ETHERSCAN_API_KEY \
--ledger \
--broadcast
```
##### Enable Earning
Once governance approval is confirmed, call `enableEarning()` on your deployed contract to begin accruing yield:
```solidity
// This starts the yield accrual process - can only be called ONCE
institutionalUSD.enableEarning();
```
**Important:** Remember that earning can only be enabled once. If you disable earning later, you cannot re-enable it.
#### 7. Application Logic & Integration
##### Backend Integration for Client Management
```javascript
// Example Node.js/Web3 integration for client onboarding
class InstitutionalUSDManager {
constructor(contract, earnerManagerSigner) {
this.contract = contract;
this.signer = earnerManagerSigner;
}
// Onboard a new institutional client
async onboardClient(clientAddress, feeRateBps) {
const tx = await this.contract.connect(this.signer).setAccountInfo(clientAddress, true, feeRateBps);
await tx.wait();
console.log(`Client ${clientAddress} onboarded with ${feeRateBps} bps fee rate`);
}
// Batch onboard multiple clients
async batchOnboardClients(clientData) {
const addresses = clientData.map((c) => c.address);
const statuses = clientData.map(() => true);
const feeRates = clientData.map((c) => c.feeRate);
const tx = await this.contract.connect(this.signer).setAccountInfo(addresses, statuses, feeRates);
await tx.wait();
console.log(`Batch onboarded ${clientData.length} clients`);
}
// Offboard a client
async offboardClient(clientAddress) {
const tx = await this.contract.connect(this.signer).setAccountInfo(clientAddress, false, 0);
await tx.wait();
console.log(`Client ${clientAddress} offboarded`);
}
// Update client fee rate
async updateClientFeeRate(clientAddress, newFeeRate) {
const tx = await this.contract.connect(this.signer).setAccountInfo(clientAddress, true, newFeeRate);
await tx.wait();
console.log(`Updated ${clientAddress} fee rate to ${newFeeRate} bps`);
}
// Claim yield for a client
async claimYieldForClient(clientAddress) {
const tx = await this.contract.claimFor(clientAddress);
const receipt = await tx.wait();
// Parse events to get yield amounts
const yieldEvent = receipt.events.find((e) => e.event === 'YieldClaimed');
return yieldEvent.args.yield;
}
}
```
##### Client Management Workflows
1. **Onboarding Process:**
```solidity
// When a new client is approved on your platform
function onboardClient(address client, uint16 feeRate) external onlyAuthorized {
// Verify client through your KYC/compliance process
require(isKYCApproved(client), "Client not KYC approved");
// Whitelist them on the contract
institutionalUSD.setAccountInfo(client, true, feeRate);
emit ClientOnboarded(client, feeRate);
}
```
2. **Offboarding Process:**
```solidity
// When a client relationship ends
function offboardClient(address client) external onlyAuthorized {
// Claim any pending yield first
institutionalUSD.claimFor(client);
// Remove from whitelist
institutionalUSD.setAccountInfo(client, false, 0);
emit ClientOffboarded(client);
}
```
3. **Automated Yield Distribution:**
```solidity
// Periodic yield claiming for all clients
function distributeYieldToAllClients() external onlyAuthorized {
address[] memory clients = getActiveClients();
for (uint i = 0; i < clients.length; i++) {
uint256 accruedYield = institutionalUSD.accruedYieldOf(clients[i]);
if (accruedYield > minYieldThreshold) {
institutionalUSD.claimFor(clients[i]);
}
}
}
```
##### Frontend Integration for Clients
```javascript
// Client dashboard showing their institutional account
// Check if user is whitelisted
const isWhitelisted = await institutionalUSD.isWhitelisted(clientAddress);
if (isWhitelisted) {
// Get client's fee rate
const feeRate = await institutionalUSD.feeRateOf(clientAddress);
// Get current balance
const balance = await institutionalUSD.balanceOf(clientAddress);
// Get balance including unclaimed yield
const balanceWithYield = await institutionalUSD.balanceWithYieldOf(clientAddress);
// Get detailed yield information
const [yieldWithFee, fee, yieldNetOfFee] = await institutionalUSD.accruedYieldAndFeeOf(clientAddress);
// Get principal amount
const principal = await institutionalUSD.principalOf(clientAddress);
// Display to client
console.log(`Balance: ${balance}`);
console.log(`Total with yield: ${balanceWithYield}`);
console.log(`Unclaimed yield (net): ${yieldNetOfFee}`);
console.log(`Fee rate: ${feeRate / 100}%`);
console.log(`Principal: ${principal}`);
}
```
#### 8. Monitoring & Maintenance
##### Key Metrics to Track
* **Active Clients**: Number of whitelisted addresses
* **Fee Revenue**: Total fees collected by the protocol
* **Client Yield**: Individual and aggregate yield distributed to clients
* **Average Fee Rate**: Weighted average fee rate across all clients
* **Total Principal**: Total principal amount across all clients
##### Operational Considerations
* **Regular Yield Claims**: Set up automated processes to claim yield for clients regularly
* **Fee Rate Optimization**: Monitor and adjust individual client fee rates based on business relationships
* **Compliance Monitoring**: Track all whitelist changes for audit trails
* **Gas Optimization**: Batch operations when possible to reduce transaction costs
##### Administrative Functions
```javascript
// Example admin functions for ongoing management
// Update the global fee recipient
await institutionalUSD.setFeeRecipient(newTreasuryAddress);
// Get contract state information
const feeRecipient = await institutionalUSD.feeRecipient();
const totalPrincipal = await institutionalUSD.totalPrincipal();
const projectedSupply = await institutionalUSD.projectedTotalSupply();
const isEarningEnabled = await institutionalUSD.isEarningEnabled();
const wasEarningEnabled = await institutionalUSD.wasEarningEnabled();
const disableIndex = await institutionalUSD.disableIndex();
const currentIndex = await institutionalUSD.currentIndex();
// Batch claim for multiple clients
const clients = [client1, client2, client3];
const [yieldWithFees, fees, yieldNetOfFees] = await institutionalUSD.claimFor(clients);
```
##### Error Handling
Be prepared to handle these specific errors:
```javascript
// Handle MEarnerManager specific errors
try {
await institutionalUSD.setAccountInfo(client, true, feeRate);
} catch (error) {
if (error.message.includes('NotWhitelisted')) {
console.log('Account not whitelisted for this operation');
} else if (error.message.includes('InvalidFeeRate')) {
console.log('Fee rate exceeds maximum allowed (10,000 bps)');
} else if (error.message.includes('InvalidAccountInfo')) {
console.log('Cannot set non-zero fee rate for non-whitelisted account');
}
}
```
**Congratulations!** You now have a fully functional institutional-grade stablecoin that provides:
* **Granular Control**: Individual fee rates and whitelisting for each client
* **Compliance Ready**: Built-in permissioning system for regulatory requirements
* **Revenue Generation**: Customizable fee structures for different client tiers
* **Professional Grade**: Enterprise-level access control and audit trails
* **One-Time Earning**: Secure earning mechanism that can only be enabled once
### SVM Implementation
Implementation guide for the Crank model on Solana and SVM chains coming soon.
import ZoomableImage from '@/components/ZoomableImage';
## Institutional Model
**Core Concept:** Three-tier permissioned architecture where an admin delegates authority to Earn Managers, who manage
their own sets of earners with custom fee arrangements.
**When to Use:**
* Institutional platforms serving multiple client organizations
* B2B services requiring bespoke yield-sharing agreements
* Prime brokerages with per-client fee structures
* Platforms needing granular control over who can hold and earn yield
### EVM Implementation: MEarnerManager
The `MEarnerManager` extension is a powerful model designed for **institutional, B2B, and permissioned use cases**. It
provides granular, per-account control over who can hold the token and what share of the yield they receive.
This model is the perfect choice for fintech platforms, prime brokerages, or any application serving a limited number of
clients who require bespoke, onchain enforced fee structures. Its core feature is a **whitelist**: only approved
addresses can interact with the token.
**Source Code:**
[`MEarnerManager.sol`](https://github.com/m0-foundation/m-extensions/blob/main/src/projects/earnerManager/MEarnerManager.sol)
#### Architecture and Mechanism
The architecture of `MEarnerManager` revolves around a central manager role that curates a list of approved "earners"
with individual fee arrangements.
##### Roles (Access Control)
* **`DEFAULT_ADMIN_ROLE`**: The contract administrator, responsible for granting and revoking roles.
* **`EARNER_MANAGER_ROLE`**: The gatekeeper and controller of the extension's economics. This role is responsible for:
* Whitelisting and de-listing accounts via `setAccountInfo`
* Setting a custom `feeRate` for each whitelisted account (0-10,000 basis points)
* Setting the global `feeRecipient` address that collects all fees
##### How It Works
1. **Whitelisting is Mandatory:** Before any address can hold, receive, wrap, unwrap, approve, or transfer the token, it
**must** be added to the whitelist by the `EARNER_MANAGER_ROLE` via the `setAccountInfo` function.
2. **Custom Fee Rates:** When an account is whitelisted, the manager assigns it a specific `feeRate` (from 0% to 100%).
This allows for different commercial agreements with different clients.
3. **Earning Control:** The contract can only enable earning **once** using `enableEarning()`. Once disabled, it cannot
be re-enabled (enforced by the `EarningCannotBeReenabled` error).
4. **Yield Accrual:** The contract uses a principal-based system. Each user has a `principal` amount and their yield is
calculated based on the difference between their present amount and current principal balance.
5. **On-Demand Claiming:** Yield for a specific user is only realized when `claimFor(account)` is called. This can be
called by anyone on behalf of the whitelisted account.
6. **Fee Splitting:** The `claimFor` function calculates the total yield for the account since its last claim, then
splits it:
* The user receives `Total Yield * (1 - feeRate)`
* The `feeRecipient` receives `Total Yield * feeRate`
This mechanism provides ultimate control and ensures that all token interactions are confined to a known set of
participants.
#### Key Interface & Functions
##### Storage Layout
The storage is designed around the `Account` struct, keeping each user's data neatly organized.
```solidity
// Each whitelisted user has an Account struct
struct Account {
uint256 balance; // Current token balance
bool isWhitelisted; // Whitelist status
uint16 feeRate; // Fee rate in basis points (0-10,000)
uint112 principal; // Principal amount for yield calculation
}
struct MEarnerManagerStorageStruct {
address feeRecipient;
uint256 totalSupply;
uint112 totalPrincipal;
bool wasEarningEnabled;
uint128 disableIndex;
mapping(address account => Account) accounts;
}
```
##### Management Functions (The Control Panel)
These are the primary functions used by the `EARNER_MANAGER_ROLE`.
* **`setAccountInfo(address account, bool status, uint16 feeRate) external`**: The main function to manage the
whitelist. It can add an account, remove it, or update its `feeRate`. Claims yield for the account before making
changes. Reverts with `InvalidAccountInfo` if trying to set a non-zero fee rate for a non-whitelisted account.
* **`setAccountInfo(address[] calldata accounts, bool[] calldata statuses, uint16[] calldata feeRates) external`**: A
batch version for managing multiple accounts in one transaction. Reverts with `ArrayLengthMismatch` if array lengths
don't match or `ArrayLengthZero` if arrays are empty.
* **`setFeeRecipient(address feeRecipient_) external`**: Sets or updates the single address that will receive all
collected fees. The fee recipient is automatically whitelisted with a 0% fee rate.
##### Core Yield & Claim Functions
* **`claimFor(address account) external`**: The function to trigger a yield claim and fee split for a specific
whitelisted account. Returns `(yieldWithFee, fee, yieldNetOfFee)`. Can be called by anyone.
* **`claimFor(address[] calldata accounts) external`**: Batch version that claims yield for multiple accounts. Returns
arrays of yield amounts, fees, and net yields.
##### Earning Control Functions
* **`enableEarning() external`**: Starts earning yield. Can only be called once - if earning was previously enabled, it
reverts with `EarningCannotBeReenabled`.
* **`disableEarning() external`**: Stops earning yield and records the disable index. Reverts with `EarningIsDisabled`
if already disabled.
##### View Functions
* **`isWhitelisted(address account) external view`**: A simple check to see if an account is on the approved list. This
is used as a gate for all transfers, wraps, and unwraps.
* **`feeRateOf(address account) external view`**: Returns the custom fee rate for a specific account.
* **`principalOf(address account) external view`**: Returns the principal amount for an account.
* **`accruedYieldAndFeeOf(address account) external view`**: Calculates the current claimable yield, the fee portion,
and the net yield for an account without executing the claim. Returns `(yieldWithFee, fee, yieldNetOfFee)`.
* **`accruedYieldOf(address account) external view`**: Returns only the net yield after fees for an account.
* **`accruedFeeOf(address account) external view`**: Returns only the fee portion of the yield for an account.
* **`balanceWithYieldOf(address account) external view`**: Returns the account's current balance plus accrued net yield.
* **`feeRecipient() external view`**: Returns the current fee recipient address.
* **`totalPrincipal() external view`**: Returns the total principal of all accounts.
* **`projectedTotalSupply() external view`**: Shows what the total supply would be if all yield was claimed.
* **`disableIndex() external view`**: Returns the index when earning was disabled (0 if never disabled).
* **`wasEarningEnabled() external view`**: Returns whether earning was ever enabled.
* **`isEarningEnabled() external view`**: Returns whether earning is currently enabled (was enabled and not disabled).
* **`currentIndex() external view`**: Returns the current index for yield calculations. Uses the disable index if
earning is disabled, otherwise uses the $M token's current index.
##### Access Control Enforcement
All operations (wrap, unwrap, transfer, approve) enforce that **all involved parties are whitelisted**:
* `_beforeApprove()` - Checks both account and spender are whitelisted
* `_beforeWrap()` - Checks earning is enabled and both depositor and recipient are whitelisted
* `_beforeUnwrap()` - Checks the account is whitelisted
* `_beforeTransfer()` - Checks msg.sender, sender, and recipient are all whitelisted
##### Constants
* **`ONE_HUNDRED_PERCENT`**: Set to 10,000 (representing 100% in basis points)
* **`EARNER_MANAGER_ROLE`**: `keccak256("EARNER_MANAGER_ROLE")`
##### Error Handling
The contract includes comprehensive error handling:
* `NotWhitelisted(address account)`: Thrown when a non-whitelisted account tries to interact
* `EarningCannotBeReenabled`: Thrown when trying to enable earning after it was already enabled once
* `InvalidFeeRate`: Thrown when fee rate exceeds 100%
* `InvalidAccountInfo`: Thrown when trying to set non-zero fee rate for non-whitelisted account
* `ZeroAdmin`: Thrown when admin address is 0x0
* `ZeroEarnerManager`: Thrown when earner manager address is 0x0
* `ZeroFeeRecipient`: Thrown when fee recipient address is 0x0
* `ZeroAccount`: Thrown when account address is 0x0
* `ArrayLengthMismatch`: Thrown when array lengths don't match in batch operations
* `ArrayLengthZero`: Thrown when arrays are empty in batch operations
**Ready to build?**
[**Follow the implementation guide to deploy your MEarnerManager extension.**](/build/models/institutional/guide/)
### SVM Implementation: Crank
The `Crank` extension model is designed for **institutional and B2B platforms** that require granular control over who
can earn yield and how that yield is distributed. It implements a **three-tier permissioning hierarchy** where an admin
delegates authority to Earn Managers, who in turn manage their own sets of earners.
This model is used by **Wrapped $M (wM)**, the first-party M0 extension on Solana, and serves as the blueprint for any
protocol serving institutional clients or requiring bespoke yield-sharing agreements.
**Source Code:** The `Crank` model is compiled from the `m_ext` program using the `crank` Rust feature flag. The source
code is available in the [solana-m-extensions](https://github.com/m0-foundation/solana-m-extensions) repository.
**Migration Note:** In V2, wM migrated from the legacy `ext_earn` program to the unified `m_ext` framework using the
crank feature flag.
#### Architecture and Mechanism
The `Crank` model implements a sophisticated three-tier hierarchy for yield management:
##### Three-Tier Hierarchy
1. **Admin** (Top Level)
* Root authority for the extension
* Can add/remove Earn Managers
* Can set the Earn Authority
* Manages core protocol configuration
2. **Earn Managers** (Middle Tier)
* Delegated authorities approved by Admin
* Each manager can add/remove their own earners
* Can configure custom fee rates (in basis points)
* Can specify fee recipient token accounts
* Useful for protocols, DAOs, or institutional partners managing their own communities
3. **Earners** (User Level)
* Individual accounts authorized to earn yield
* Managed by a specific Earn Manager
* Can set custom yield recipient addresses
* Subject to their Earn Manager's fee rate
##### Roles (Access Control)
* **`admin`**: The root administrator, typically a secure multi-sig or governance contract.
* **`earn_authority`**: A permissioned key that executes the yield distribution crank (typically an offchain bot).
* **`earn_manager`**: An intermediary authority that can manage a subset of earners and charge fees.
* **`earner`**: An individual account approved to hold the extension token and earn yield.
* **`wrap_authority`**: Addresses authorized to perform wrap/unwrap operations (e.g., swap facility).
#### How It Works: Crank-Based Yield Distribution
Unlike the ScaledUi model where yield is automatically distributed via rebasing, the Crank model uses an **offchain
calculation + onchain distribution** pattern:
##### Yield Distribution Flow
:::steps
##### Step 1: Yield Accrues in Vault
The extension's `m_vault` holds value that is registered as an earner in the base program. As the index updates, the
vault's balance grows.
##### Step 2: Offchain Balance Calculation
The `earn_authority` (typically an offchain bot) calculates the weighted average balance for each earner since their
last claim. This ensures yield is distributed fairly even if balances change during the distribution period.
##### Step 3: Onchain Claiming (The "Crank")
The `earn_authority` iterates through eligible earners and calls `claim_for(earner, calculated_balance)` for each one.
This instruction:
* Calculates yield owed based on the snapshot balance
* Deducts the Earn Manager's fee (if configured)
* Mints new extension tokens to the earner's recipient account
* Mints fee tokens to the Earn Manager's fee recipient account
* Updates the earner's `last_index` and `last_balance`
##### Step 4: Continuous Operation
The crank runs periodically (e.g., daily or weekly) to distribute newly accrued yield to all active earners.
:::
#### Key State Structures
##### ExtGlobal Account
The top-level configuration for the extension.
```rust
pub struct ExtGlobal {
pub admin: Pubkey,
pub earn_authority: Option,
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,
}
pub struct YieldConfig {
pub variant: YieldVariant, // Set to YieldVariant::Crank
pub last_m_index: u64,
pub last_ext_index: u64,
}
```
##### EarnManager Account
Represents a delegated authority managing a set of earners.
```rust
pub struct EarnManager {
pub manager: Pubkey, // The manager's authority
pub fee_bps: u64, // Fee rate in basis points (e.g., 500 = 5%)
pub fee_token_account: Pubkey, // Where manager fees are sent
pub bump: u8,
}
```
##### Earner Account
Tracks an individual earner's yield distribution state.
```rust
pub struct Earner {
pub user: Pubkey, // The earner's wallet
pub user_token_account: Pubkey, // Token account earning yield
pub recipient_token_account: Pubkey, // Where yield is sent (can differ)
pub earn_manager: Pubkey, // The managing EarnManager PDA
pub last_balance: u64, // Balance at last claim
pub last_index: u64, // Index at last claim
pub bump: u8,
}
```
#### Key Instructions
##### Admin Instructions
* **`add_earn_manager(manager: Pubkey, fee_bps: u64, fee_token_account: Pubkey)`**: Adds a new Earn Manager with
specified fee configuration.
* **`remove_earn_manager(manager: Pubkey)`**: Removes an Earn Manager (earners must be removed first).
* **`set_earn_authority(new_earn_authority: Option)`**: Sets or updates the earn authority that can execute
claims.
##### Earn Manager Instructions
* **`add_earner(user: Pubkey, user_token_account: Pubkey, recipient_token_account: Pubkey)`**: Adds a new earner to this
manager's set.
* **`remove_earner(user: Pubkey)`**: Removes an earner from this manager's set.
* **`configure_earn_manager(new_fee_bps: u64, new_fee_token_account: Pubkey)`**: Updates the manager's fee
configuration.
* **`transfer_earner(user: Pubkey, new_earn_manager: Pubkey)`**: Transfers an earner to a different Earn Manager.
##### Earn Authority Instructions
* **`claim_for(user: Pubkey, snapshot_balance: u64)`**: Distributes yield to a specific earner based on the calculated
snapshot balance.
* **`sync()`**: Updates the extension's index from the base $M token program (similar to ScaledUi).
##### Earner Instructions
* **`set_recipient(new_recipient_token_account: Pubkey)`**: Allows an earner to change where their yield is sent. Useful
for DeFi integrations where tokens are locked in a contract.
##### Wrap/Unwrap
* **`wrap(amount: u64)`**: Wraps into the extension token (requires `wrap_authority`).
* **`unwrap(amount: u64)`**: Unwraps extension token (requires `wrap_authority`).
**Ready to build?**
[**Follow the implementation guide to deploy your Crank extension.**](/build/models/institutional/guide/)
## Gaining Earner Approval
For your M0 Extension to accrue yield, its deployed contract address must be approved as an **M0 Earner**. This is a fundamental security and economic feature of the M0 protocol, ensuring that yield is distributed only to recognized and approved participants.
This approval is not automatic; it is granted through a formal, onchain **M0 Governance** process. This guide will walk you through the necessary steps to prepare and submit a governance proposal to get your extension approved.
### The Governance Mechanism
Earner approval is managed by the M0 Two-Token Governance (TTG) system. Specifically:
* **The Source of Truth:** The `TTGRegistrar` contract maintains an onchain list of all approved earner addresses. This list is identified by the `bytes32` key for the string `"earners"`.
* **The Gatekeeper:** The `StandardGovernor` contract is the only entity that can add an address to this list.
* **The Process:** To get your address added, a `Standard Proposal` must be created, voted on by `POWER` token holders, and successfully executed.
The `MToken` and M0 Extension contracts check this onchain list whenever an earning-related function (like `enableEarning()` or `startEarning()`) is called.
### The Process: Step-by-Step
Follow these steps to successfully gain earner approval for your extension.
:::steps
#### Deploy Your Contract
You cannot submit a proposal for an address that doesn't exist yet. Before you can request earner approval, you **must deploy your final, audited M0 Extension contract** to the target network (e.g., Ethereum Mainnet, Arbitrum, etc.).
Once deployed, you will have the static contract address that needs to be whitelisted.
#### Prepare Your Proposal
A strong proposal is crucial for gaining the support of `POWER` token holders. Gather the following information:
* **Your Extension's Deployed Address:** The address you want to add to the earner list.
* **A Clear Title:** e.g., "Add \[Your Stablecoin Name] Extension to Earner List".
* **A Detailed Description:** This is your chance to make your case to the community. Include:
* A brief description of your project and the purpose of your stablecoin extension.
* A link to your project's website and social media.
* A link to the **independent security audit report** for your extension contract. This is extremely important for building trust.
* The specific benefits your extension brings to the M0 ecosystem.
#### Create the Governance Proposal
You can create a proposal using the [M0 Governance Portal](https://governance.m0.org/proposal/create).
1. **Connect Your Wallet:** Navigate to the [Create Proposal page](https://governance.m0.org/proposal/create) and connect your wallet.
2. **Define the Action to be Executed:** This is the first step in the proposal creation process.
* **Action Type:** Select "Add actor"
* **Role:** Choose "Earner" from the dropdown
* **Address:** Enter the deployed address of your extension contract from Step 1
3. **Name Your Proposal and Add Description:**
* **Title:** Use the clear title you prepared (e.g., "Add \[Your Stablecoin Name] Extension to Earner List")
* **Description:** Include your detailed description with:
* A brief description of your project and the purpose of your stablecoin extension
* A link to your project's website and social media
* A link to the **independent security audit report** for your extension contract
* The specific benefits your extension brings to the M0 ecosystem
4. **Submit the Proposal:** Submitting a `Standard Proposal` requires paying a `proposalFee` in the system's active `CashToken` (e.g., WETH). This fee is refunded if your proposal is successfully executed. Refer to the [governance page](https://governance.m0.org/config/governance).
#### Socialize and Campaign for Your Proposal
A proposal does not pass on its own. You need to secure votes from `POWER` token holders.
* **Announce Your Proposal:** Share your proposal to the M0 team.
* **Engage with the Community:** Be prepared to answer questions from `POWER` holders about your project's security, utility, and vision.
#### Voting and Execution
* **Voting Epoch:** Your proposal will become active for voting during the next **Voting Epoch** (an odd-numbered 15-day period).
* **Passing Criteria:** A `Standard Proposal` passes if it receives more "Yes" votes than "No" votes from participating `POWER` holders.
* **Execution:** If the proposal passes, it can be executed by anyone during the subsequent **Transfer Epoch**. Upon execution, your extension's address will be officially added to the earner list.
#### Enable Earning on Your Contract
Once your governance proposal has been successfully executed, you must perform one final action. Call the `enableEarning()` function on your own deployed extension contract.
This function will check the `TTGRegistrar` to confirm your address is now on the `earners` list and will then activate the yield accrual mechanism within your contract.
Congratulations! Your M0 Extension is now a yield-bearing asset.
:::
## User Guide: Bridging $M and w$M Tokens
This guide explains how to manually bridge $M and wrapped $M (w$M) tokens between Ethereum (the "Hub") and connected networks (the "Spokes") using a block explorer.
**Important:** The bridging system uses two different portal contracts depending on the destination chain. Always use the correct Portal address for your transaction.
**Prerequisites:**
* A Web3 wallet (e.g., MetaMask).
* Your wallet must be funded with the token you wish to bridge.
* Your wallet must have the native currency of the source chain for gas fees (e.g., ETH on Ethereum).
### Addresses & Chain IDs
#### Portal Contract Addresses
*Which portal to use based on your destination chain*
| Portal Type | Supported Chains | Contract Address |
| --------------------------- | ------------------------------ | -------------------------------------------- |
| **Portal (NTT/Wormhole)** | Arbitrum, Optimism | `0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd` |
| **Portal Lite (Hyperlane)** | Plume, HyperEVM, Linea, Mantra | `0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE` |
#### Token Addresses
*Same addresses across all supported chains*
| Token | Address |
| ------------- | -------------------------------------------- |
| **$M Token** | `0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b` |
| **w$M Token** | `0x437cc33344a0B27A429f795ff6B469C72698B291` |
> Note on Amounts: $M and w$M tokens use 6 decimal places. When entering an amount in a contract, add 6 zeros. To transfer 10 tokens, enter 10000000.
#### Chain IDs
| Chain | EVM Chain ID | Wormhole Chain ID | Hyperlane |
| ---------------- | ------------ | ----------------- | --------- |
| **Ethereum** | `1` | `2` | - |
| **Arbitrum One** | `42161` | `23` | - |
| **Optimism** | `10` | `24` | - |
| **Plume** | `98866` | — | ✅ |
| **HyperEVM** | `999` | — | ✅ |
| **Mantra** | `5888` | — | ✅ |
| **Linea** | `59144` | — | ✅ |
### **Part 1: Hub to Spoke (Bridging from Ethereum)**
The process always involves two transactions: `approve` and `transferMLikeToken`.
#### A. Bridging to `Arbitrum` or `Optimism` (via M Portal)
This uses the **Portal (NTT/Wormhole)**. The steps are the same for bridging either w$M or $M.
:::steps
##### Step 1: Approve the Portal
1. Navigate to the token contract you want to bridge on Etherscan:
* For **w$M**: [`0x437c...98B291`](https://etherscan.io/address/0x437cc33344a0B27A429f795ff6B469C72698B291)
* For **$M**: [`0x866A...36be1b`](https://etherscan.io/address/0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b)
2. Go to the `Contract` → `Write as Proxy` (for w$M) or `Write Contract` (for $M) tab.
3. `Connect to Web3` with your wallet.
4. Find the `approve` function and enter:
* `spender (address)`: The **Portal (NTT)** address: `0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd`.
* `amount (uint256)`: The amount to bridge, with 6 extra zeros.
5. Click `Write` and confirm the transaction.
##### Step 2: Transfer via the Portal
1. Navigate to the **Portal (NTT) on Etherscan**: `0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd`.
2. Go to the `Contract` → `Write as Proxy` tab and connect your wallet.
3. Find the **`transferMLikeToken`** function and enter:
* `transferMLikeToken (payableAmount)`: The cross-chain delivery fee in ETH. This must be quoted from the Wormhole relayer network.
* `amount (uint256)`: The **exact same amount** you approved.
* `sourceToken (address)`: The address of the token you are sending from Ethereum (e.g., `0x437cc33344a0B27A429f795ff6B469C72698B291` for w$M).
* `destinationChainId (uint16)`: The **Wormhole Chain ID** for your destination (23 for Arbitrum, `24` for Optimism).
* `destinationToken (bytes32)`: The `bytes32` address of the token you want to receive. For w$M and $M, this is `0x000000000000000000000000`.
* `recipient (bytes32)`: Your wallet address, converted to `bytes32`. Use a tool like [Chaintool](https://chaintool.org/tools/address-converter).
* `refundAddress (bytes32)`: Your wallet address, converted to `bytes32`.
4. Click `Write` and confirm. Track on [Wormhole Scan](https://wormholescan.io/).
:::
#### B. Bridging to `Plume` or `HyperEVM` (via M Portal Lite)
This uses the **Portal Lite (Hyperlane)**. The steps are the same for bridging either w$M or $M.
:::steps
##### Step 1: Approve the Portal
1. Navigate to the token contract you want to bridge on Etherscan ($M or w$M).
2. Follow the approval steps as above (A, Step 1), but for the `spender (address)`, use the **Portal Lite** address: `0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE`.
##### Step 2: Transfer via the Portal
1. Navigate to the **Portal Lite on Etherscan**: `0x36f...2ecE`.
2. Go to the `Contract` → `Write as Proxy` tab and connect.
3. Find the **`transferMLikeToken`** function and enter:
* `transferMLikeToken (payableAmount)`: The cross-chain fee in ETH. Call `quoteTransfer` on the `Read as Proxy` tab to get an estimate.
* `amount (uint256)`: The amount you approved.
* `sourceToken (address)`: The address of the token you are sending from Ethereum (e.g., `0x866A...36be1b` for $M).
* `destinationChainId (uint256)`: The **EVM Chain ID** for your destination (`98866` for Plume, `999` for HyperEVM).
* `destinationToken (address)`: The address of the token on the destination chain (e.g., `0x866A...36be1b` for $M on HyperEVM).
* `recipient (address)`: Your wallet address on the destination chain.
* `refundAddress (address)`: Your wallet address.
4. Click `Write` and confirm. Track on the [Hyperlane Explorer](https://explorer.hyperlane.xyz/).
:::
### Part 2: Spoke to Hub (Bridging to Ethereum)
This is the reverse process. You will perform the transactions on the source chain's block explorer (e.g., Arbiscan, Plume Explorer).
* **From `Arbitrum` or `Optimism`**:
1. **Approve**: On Arbiscan/Optimistic Etherscan, go to the $M or w$M token and `approve` the **Portal (NTT)** address: `0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd`.
2. **Transfer**: Go to the Portal contract (`0xD92...28fd`) and call **`transferMLikeToken`**. Use a `destinationChainId` of `2` (Wormhole ID for Ethereum).
* **From `Plume` or `HyperEVM`**:
1. **Approve**: On the source explorer, go to the $M or w$M token and `approve` the **Portal Lite** address: `0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE`.
2. **Transfer**: Go to the Portal Lite contract (`0x36f...2ecE`) and call **`transferMLikeToken`**. Use a `destinationChainId` of `1` (EVM ID for Ethereum).
## **Developer's Guide: Integrating with the M Portals**
This guide provides technical details and step-by-step workflows for developers integrating with the M0 token bridging infrastructure. The system uses two distinct protocols for different chains, so it is crucial to use the correct integration path.
* **Portal (NTT) Repository**: [m-portal on GitHub](https://github.com/m0-foundation/m-portal/blob/main/README.md)
* **Portal Lite (Hyperlane) Repository**: [m-portal-lite on GitHub](https://github.com/m0-foundation/m-portal-lite/blob/main/README.md)
The Portal system is built on the Wormhole Native Token Transfer (NTT) standard. It is used for bridging tokens to and from **Arbitrum One** and **Optimism**.
### **Key Concepts**
* **Protocol:** Wormhole NTT
* **Chains:** `Ethereum` ↔ `Arbitrum One`, `Ethereum` ↔ `Optimism`
* **Chain IDs:** Uses **Wormhole Chain IDs** (`uint16`), e.g., Ethereum = `2`, Arbitrum = `23`, Optimism = `24`.
* **Addresses:** Recipient and token addresses are passed as `bytes32`.
### **Contract Addresses**
| Contract | Network(s) | Address |
| ------------------------ | ------------------ | -------------------------------------------- |
| **Hub Portal** | Ethereum | `0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd` |
| **Spoke Portal** | Arbitrum, Optimism | `0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd` |
| **Wormhole Transceiver** | All | `0x0763196A091575adF99e2306E5e90E0Be5154841` |
### M Portal - Hub to Spoke (e.g., Ethereum → Arbitrum)
This workflow details how to lock a token (M or wM) on Ethereum and mint its equivalent on a spoke chain like Arbitrum.
:::steps
#### **Step 1: Approve the Portal Contract**
Before the Portal can transfer your tokens, you must grant it an allowance.
```solidity
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Addresses for the w$M example
address constant W_M_ETHEREUM = 0x437cc33344a0B27A429f795ff6B469C72698B291;
address constant HUB_PORTAL = 0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd;
uint256 amount = 100 * 1e6; // 100 w$M
// Approve the Hub Portal to spend your tokens
IERC20(W_M_ETHEREUM).approve(HUB_PORTAL, amount);
```
#### **Step 2: Quote the Delivery Fee**
The Wormhole NTT protocol requires a fee to pay for cross-chain relaying. To get this fee, call the `quoteDeliveryPrice` function on the Portal contract. The quoted amount must be sent as `msg.value` in the `transferMLikeToken` call to ensure the transaction is delivered.
The function takes two parameters:
* `recipientChain` (uint16): The destination Wormhole Chain ID (e.g., `23` for Arbitrum).
* `transceiverInstructions` (bytes): This parameter is not used and **must be passed as an empty bytes array**.
```solidity
// The second argument (transceiverInstructions) is unused and must be empty.
(, uint256 deliveryFee) = IManagerBase(HUB_PORTAL).quoteDeliveryPrice(
destinationChainId,
bytes("") // or new bytes(0)
);
```
**Note:** For testing, you can often use a nominal value, but production applications **must** query the correct fee to ensure transaction delivery.
#### **Step 3: Call `transferMLikeToken`**
This function initiates the bridging process. All addresses must be converted to `bytes32`.
```solidity
import { IHubPortal } from "./interfaces/IHubPortal.sol";
import { TypeConverter } from "./libs/TypeConverter.sol"; // Assumed helper
// Addresses and IDs for the w$M example
address constant W_M_ETHEREUM = 0x437cc33344a0B27A429f795ff6B469C72698B291;
address constant W_M_ARBITRUM = 0x437cc33344a0B27A429f795ff6B469C72698B291;
address constant HUB_PORTAL = 0xD925C84b55E4e44a53749fF5F2a5A13F63D128fd;
uint16 constant ARBITRUM_WORMHOLE_ID = 6;
uint256 amount = 100 * 1e6; // 100 w$M
uint256 deliveryFee = ...; // From Step 2
IHubPortal(HUB_PORTAL).transferMLikeToken{value: deliveryFee}(
amount,
W_M_ETHEREUM, // sourceToken
ARBITRUM_WORMHOLE_ID, // destinationChainId
TypeConverter.toBytes32(W_M_ARBITRUM), // destinationToken
TypeConverter.toBytes32(msg.sender), // recipient
TypeConverter.toBytes32(msg.sender) // refundAddress
);
```
:::
### M Portal - Spoke to Hub (e.g., Arbitrum → Ethereum)
This workflow is the mirror image of the first. You will interact with the Spoke Portal contract on the source chain (e.g., Arbitrum).
1. **Approve:** Call `approve` on the token contract on the spoke chain, granting an allowance to the Spoke Portal (`0xD92...28fd`).
2. **Quote Fee:** Obtain the delivery fee for sending a message from the spoke chain to Ethereum.
3. **Bridge:** Call `transferMLikeToken` on the Spoke Portal.
* `destinationChainId`: `2` (Wormhole ID for Ethereum).
* `destinationToken`: The `bytes32` address of the target token on Ethereum.
The Portal Lite system is built on the Hyperlane protocol. It is used for bridging tokens to and from **Plume** and **HyperEVM**.
#### **Key Concepts**
* **Protocol:** Hyperlane
* **Chains:** `Ethereum` ↔ `Plume`, `Ethereum` ↔ `HyperEVM`
* **Chain IDs:** Uses standard **EVM Chain IDs** (`uint256`), e.g., Ethereum = `1`, Plume = `98866`.
* **Addresses:** All addresses are standard `address` types.
#### **Contract Addresses**
| Contract | Network(s) | Address |
| --------------- | ------------------------- | -------------------------------------------- |
| **Portal Lite** | Ethereum, Plume, HyperEVM | `0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE` |
### M Portal Lite - Hub to Spoke (e.g., Ethereum → Plume)
This workflow details locking a token on Ethereum and minting its equivalent on Plume.
:::steps
#### **Step 1: Quote the Delivery Fee**
Hyperlane allows onchain quoting of the message delivery fee. Call this function first to determine the required `msg.value`.
```solidity
import { IPortal } from "./interfaces/IPortal.sol"; // From m-portal-lite
address constant PORTAL_LITE = 0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE;
uint256 constant PLUME_CHAIN_ID = 98866;
uint256 amount = 100 * 1e6;
address recipient = msg.sender;
uint256 deliveryFee = IPortal(PORTAL_LITE).quoteTransfer(
amount,
PLUME_CHAIN_ID,
recipient
);
```
#### **Step 2: Approve the Portal Contract**
Grant the Portal Lite contract an allowance to spend your tokens.
```solidity
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
address constant W_M_ETHEREUM = 0x437cc33344a0B27A429f795ff6B469C72698B291;
address constant PORTAL_LITE = 0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE;
uint256 amount = 100 * 1e6;
IERC20(W_M_ETHEREUM).approve(PORTAL_LITE, amount);
```
#### **Step 3: Call `transferMLikeToken`**
This function initiates the bridging process, using the fee quoted in Step 1.
```solidity
import { IPortal } from "./interfaces/IPortal.sol"; // From m-portal-lite
// Addresses, IDs, and amounts from previous steps
address constant W_M_ETHEREUM = 0x437cc33344a0B27A429f795ff6B469C72698B291;
address constant W_M_PLUME = 0x437cc33344a0B27A429f795ff6B469C72698B291;
address constant PORTAL_LITE = 0x36f586A30502AE3afb555b8aA4dCc05d233c2ecE;
uint256 constant PLUME_CHAIN_ID = 98866;
uint256 amount = 100 * 1e6;
uint256 deliveryFee = ...; // From Step 1
IPortal(PORTAL_LITE).transferMLikeToken{value: deliveryFee}(
amount,
W_M_ETHEREUM, // sourceToken
PLUME_CHAIN_ID, // destinationChainId
W_M_PLUME, // destinationToken
msg.sender, // recipient
msg.sender // refundAddress
);
```
:::
### M Portal Lite - Spoke to Hub (e.g., Plume → Ethereum)
The process is identical to Workflow 1 but performed on the spoke chain's contracts.
1. **Quote Fee:** Call `quoteTransfer` on the Portal Lite contract on Plume to get the fee for delivering a message to Ethereum (`destinationChainId = 1`).
2. **Approve:** Call `approve` on the token contract on Plume, granting an allowance to the Portal Lite (`0x36f...2ecE`).
3. **Bridge:** Call `transferMLikeToken` on the Portal Lite on Plume, with `destinationChainId` set to `1`.
## Token Overview
This guide explains how to retrieve token data for M0 protocol tokens. The API provides metadata, holders, and supply information for multiple tokens including:
* **$M**
* **POWER**
* **ZERO**
### Query Multiple Tokens
Use the `tokens` query to retrieve metadata, supply data, and holder information for all tokens:
```graphql
query TokensOverview {
tokens {
id
name
symbol
decimals
totalSupplys(first: 2, orderBy: blockTimestamp, orderDirection: desc) {
value
blockTimestamp
}
holders(first: 10, orderBy: balance, orderDirection: desc) {
address
balance
}
}
}
```
This query returns:
* Token metadata (name, symbol, decimals, contract address)
* Historical total supply snapshots
* Top token holders and their balances
Use the `token` query to retrieve data for a specific token:
```graphql
query TokenOverview($id: String!) {
token(id: $id) {
id
name
symbol
decimals
}
}
```
### Important: Indexer Data Lag
The supply data returned by the indexer represents the last-seen state and may not reflect the absolute latest on-chain data. There can be a delay between when a transaction occurs on-chain and when the indexer processes it.
### Querying Supply Directly from Chain
For the most up-to-date supply data, query the blockchain directly via RPC:
```typescript
// Using viem, ethers.js or web3.js
const totalSupply = await tokenContract.totalSupply()
```
Note that the method to retrieve the total supply may vary between contracts. Before querying the blockchain directly, review the contract's ABI and documentation to ensure you're using the correct method.
You can find our token contracts on [Deployments](/get-started/resources/addresses/).
## Token Holders
This guide explains how to query holder information across different chains in the M0 ecosystem. Each chain features M0-powered stablecoins that are included in the holders response.
### Ethereum
Query current $M holders on Ethereum with their balances and accrued yields:
```graphql
query MHoldersQuery {
MHolders(first: 100) {
address
balance
accruedYield
}
}
```
#### Featured Stablecoins on Ethereum
The holders response includes the following M0-powered stablecoins:
* **$M (Wrapped)** (WM) - `0x437cc33344a0b27a429f795ff6b469c72698b291`
* **Metamask USD** (mUSD) - `0xacA92E438df0B2401fF60dA7E4337B687a2435DA`
* **Noble** (USDN) - `0x83Ae82Bd4054e815fB7B189C39D9CE670369ea16`
* **Usual** (UsualM) - `0x4Cbc25559DbBD1272EC5B64c7b5F48a2405e6470`
### Arbitrum
Query current $M holders on Arbitrum with their balances and accrued yields:
```graphql
query WMHoldersArbitrumQuery {
WMHoldersArbitrum(first: 100) {
address
balance
accruedYield
claimedYield
unclaimedYield
}
}
```
#### Featured Stablecoins on Arbitrum
The holders response includes the following M0-powered stablecoins:
* **USDai** (USDai) - `0x83Ae82Bd4054e815fB7B189C39D9CE670369ea16`
### Hyperliquid
Query a specific $M holder on Hyperliquid:
```graphql
query HyperliquidHolderQuery {
hyperliquid_holder {
address
isEarning
accruedYield
}
}
```
#### Featured Stablecoins on Hyperliquid
The holders response includes the following M0-powered stablecoins:
* USDhl (USDhl) - `0xb50A96253aBDF803D85efcDce07Ad8becBc52BD5`
### Linea
Query a specific $M holder on Linea:
```graphql
query LineaHolderQuery {
linea_holders {
address
isEarning
accruedYield
}
}
```
#### Featured Stablecoins on Linea
The holders response includes the following M0-powered stablecoins:
* **Metamask USD** (mUSD) - `0xacA92E438df0B2401fF60dA7E4337B687a2435DA`
### Solana
Coming soon.
## Protocol Configuration
Get historical values for protocol configuratio. Current values are feature in the [Governance app](https://governance.m0.org/config/protocol) too.
The configuration includes the following keys:
* `update_collateral_interval`
* `update_collateral_threshold`
* `penalty_rate`
* `mint_delay`
* `mint_ttl`
* `mint_ratio`
* `minter_freeze_time`
* `base_minter_rate`
* `max_earner_rate`
* `guidance`
* `minter_rate_model`
* `earner_rate_model`
To query them all:
```graphql
query ProtocolConfigQuery {
protocolConfigs {
id
value
key
blockTimestamp
}
}
```
Example output:
```json
{
"data": {
"protocolConfigs": [
{
"id": "0x25b657dfec5d4c889e0b4050224deaaa00c3707590a7e46c536d6218d673078846010000",
"value": "108000",
"key": "update_collateral_interval",
"blockTimestamp": "1715827991"
},
{
"id": "0x2637ef87d12653c1d02fff79ad1de0ead33bf13e6f8c475b5efb15bc8547f74ea1000000",
"value": "5",
"key": "penalty_rate",
"blockTimestamp": "1715828219"
},
{
"id": "0x268e7cf3cfe0d5c4e8b8b62cbeb8ae36497cb7c3f3f75d2d788150e1c97b407d2a010000",
"value": "10800",
"key": "mint_ttl",
"blockTimestamp": "1715828387"
}
]
}
}
```
Use filters to narrow down data. E.g., filter by `max_earner_rate`
```graphql
query ProtocolConfigQuery {
protocolConfigs(where: { key: "max_earner_rate" }) {
id
value
key
blockTimestamp
}
}
```
## Network Supply
Use this query to get the current network supply and the reserves (collateral) of the M0 Protocol.
\:::info Heads up! supply is currently only available on Ethereum. See Limitations below. :::
```graphql
query NetworkSupplyQuery($from: String!, $to: String!) {
supply: totalOwedMs(orderBy: blockTimestamp, orderDirection: desc, first: 1) {
amount
}
collateral: CollateralTimeSeriesGroupByType(from: $from, to: $to) {
eligibleTotal
}
}
```
Example output with `from` and `to` variables set to the last "2025-10-03":
```json
{
"data": {
"supply": [
{
"amount": "779753099463256"
}
],
"collateral": [
{
"eligibleTotal": 797237488334889
}
]
}
}
```
As you noticed, the collateral query is a time series query, so you can get the collateral information for a specific
time range.
For detailed breakdowns of collateral by remaining term and on-chain tokens, see
[Collateral composition](/api/recipes/collateral-composition/).
#### Limitations
Note that network supply is currently only available for Ethereum through the Protocol API. Support for other chains
where $M is present (Arbitrum, Hyperliquid, etc.) is coming soon.
## Minter Daily Expenses
This query will return the daily expenses of the Minter identified by `address`. The date range starts at `from` until the current time.
```graphql
query MinterExpensesQuery($address: String!, $from: String) {
MinterExpenses(address: $address, from: $from) {
amount
timestamp
date: timestampAsDate
}
}
```
### Example
Get the Minter expenses for MXON `0x7F7489582b64ABe46c074A45d758d701c2CA5446` since January 1st, 2025.
\`variables\`\`
```json
{
"address": "0x7F7489582b64ABe46c074A45d758d701c2CA5446",
"from": "01 Jan 2025"
}
```
When `from` is not specified, it defaults to the last 30 days.
## Earner rate history
This query will return snapshots of the $M earner rate
```graphql
query EarnerRateSnapshotsQuery {
rates: mToken_latestRateSnapshots(
orderDirection: desc
orderBy: timestamp
first: 4
) {
value
timestamp
}
}
```
Example output:
```json
{
"data": {
"rates": [
{
"value": "407",
"timestamp": "1747235135"
},
{
"value": "415",
"timestamp": "1747223375"
},
{
"value": "415",
"timestamp": "1747221779"
},
{
"value": "415",
"timestamp": "1747215359"
}
]
}
}
```
## Daily Yields
This guide outlines how to retrieve daily yield accrual data for various stablecoins using our GraphQL API. Depending on
the stablecoin, yield values can be queried as aggregated data for specific stablecoins or for individual holders.
### Aggregated Yield Data by Stablecoin
For most of our stablecoins, we provide aggregated yield and supply data per chain. This includes chain-specific queries
with a prefix for other stablecoins.
The snippets below use **Metamask USD (mUSD)** as an example. You can find all supported stablecoins and chain prefixes
below
#### Aggregated data
```graphql
# Fetches aggregated data for mUSD on Mainnet, Linea, and BSC
query GetMusdStablecoin {
mainnet: musd_mainnet_stablecoins {
id
claimed
minted
accruedYield
supply
lastUpdate
}
linea: musd_linea_stablecoins {
id
claimed
minted
accruedYield
supply
lastUpdate
}
bsc: musd_bsc_stablecoins {
id
claimed
minted
accruedYield
supply
lastUpdate
}
}
```
You can also get daily snapshots for yield and supply for these stablecoins.
#### Daily Yield Snapshots
```graphql
# Fetches daily yield snapshots for mUSD on Mainnet
query GetMusdDailyYields {
musd_mainnet_yieldStats_collection(interval: day) {
id
timestamp
amount
blockNumber
}
}
```
#### Daily Supply Snapshots
```graphql
# Fetches daily supply snapshots for mUSD on Mainnet
query GetMusdDailySupply {
musd_mainnet_supplyStats_collection(interval: day) {
id
timestamp
amount
blockNumber
}
}
```
:::info
You can find more supported queries in the interactive
[GraphQL Playground documentation](https://protocol-api.m0.org/graphql).
:::
#### Supported Stablecoins and Prefixes
The following table lists the stablecoins that support prefixed queries and the corresponding prefixes for each chain.
| Stablecoin | Ticker | Prefixes |
| :----------- | :-------- | :---------------------------------------------- |
| USDhl | USDHL | `usdhl_hyperevm_` |
| Braid Dollar | USDZ | `usdz_mainnet_`, `usdz_arbitrum_` |
| Dfns Rewards | 0fns | `dfns_mainnet_` |
| Startale USD | USDSC | `usdsc_soneium_` |
| MANTRA USD | mantraUSD | `mantrausd_mantra_` |
| Metamask USD | mUSD | `musd_mainnet_`, `musd_linea_`, `musd_binance_` |
| Daylight USD | GRID | `grid_plasma_` |
### Yield Snapshots for Individual Holders
For stablecoins like Noble Dollar (USDN), Wrapped $M ($wM), Usual (UsualM), and USDai, you can get daily snapshots of
accrued yield for a specific holder address.
#### For Noble Dollar (USDN)
This query returns daily snapshots of accrued yield for a given USDN holder. You can specify a starting date with the
`from` parameter. If `from` is omitted, it defaults to the last 30 days.
```graphql
# Fetches daily yield snapshots for a USDN holder
query UsdnYieldSnapshots($address: String!, $from: String) {
MHolderSnapshots(address: $address, from: $from) {
balance
accruedYield
timestamp
principal
}
}
```
**Example:** Get accrued yield snapshots for a USDN holder since April 1st, 2025.
```json
{
"address": "0x83Ae82Bd4054e815fB7B189C39D9CE670369ea16",
"from": "2025-04-01"
}
```
To get only the current values for USDN holders, you can use the `MHolders` query.
```graphql
# Fetches current yield values for the top 100 USDN holders
query GetUsdnHolders {
MHolders(first: 100) {
address
balance
accruedYield
}
}
```
#### For Wrapped $M ($wM) / Usual (UsualM)
This query returns daily snapshots of accrued yield for a given wM holder. Like the previous query, it supports the
`from` parameter and defaults to the last 30 days if omitted.
```graphql
# Fetches daily yield snapshots for a wM holder
query WmYieldSnapshots($address: String!, $from: String) {
WMHolderSnapshots(address: $address, from: $from) {
balance
accruedYield
timestamp
principal
}
}
```
**Example:** Get accrued yield snapshots for a wM holder since April 1st, 2025.
```json
{
"address": "0x4Cbc25559DbBD1272EC5B64c7b5F48a2405e6470",
"from": "2025-04-01"
}
```
#### For USDai on Arbitrum
This query retrieves current yield values for USDai holders on Arbitrum.
```graphql
# Fetches current yield values for USDai holders on Arbitrum
query GetUsdaiArbitrumData {
WMHoldersArbitrum {
address
balance
accruedYield
claimedYield
unclaimedYield
}
}
```
## Collateral Composition
Use this query to retrieve current collateral stats, daily averages, and time-series grouped by remaining term and on-chain tokens.
```graphql
query CollateralComposition(
$from: String!
$to: String!
$customGroups: [CustomGroupInput!]
) {
CollateralCurrent {
eligibleTreasuries
nonEligibleTreasuries
remainingTerm
totalCash
totalTreasuries
yieldToMaturity
totalTokenCollateral
eligibleTokenCollateral
nonEligibleTokenCollateral
}
TreasuriesTimeSeries: CollateralTimeSeries(
from: $from
to: $to
groupBy: "remainingTerm"
customGroups: $customGroups
) {
definitions {
name
}
groups {
name
datapoints {
absoluteAmount
date
relativeAmount
averageYieldToMaturity
}
}
}
OnChainCollateralTimeSeries: CollateralTimeSeries(
from: $from
to: $to
groupBy: "token"
) {
definitions {
name
}
groups {
name
datapoints {
absoluteAmount
date
relativeAmount
nav
yieldToMaturity
tokenBalance
}
}
}
}
```
### Parameters
* **from**: start time (e.g., "2025-01-01")
* **to**: end time (e.g., "2025-10-01")
* **customGroups**: optional array of groups to bucket remaining terms
#### Example `customGroups`
```json
{
name: "Short-term",
key: "remainingTerm",
values: ["0-1Y", "1-3Y"]
}
```
You can pass multiple groups to create custom buckets for the `remainingTerm` series.
## Getting started
M0 Protocol API features a GraphQL endpoint to provide information about everything related to the protocol.
### Try it yourself
You can connect to the interactive GraphQL playground from your browser:
👉 [https://protocol-api.m0.org/graphql](https://protocol-api.m0.org/graphql)
An example GrapqhQL query will be shown. Hitting the play icon will execute the query and display the request's result at right.
At right, you will also find a "Schema" and "Docs" buttons to further explore all the available queries.
### Connect your app
You can connect to the GraphQL endpoint from your app using any GraphQL or HTTP client. You will need to provide an API key. Check the Authentication page for more details.
## Changelog
Log of notable changes added to the Protocol API
### 2025-12-18
#### Deprecated
* `hyperliquid_holder` query: Migrate to `usdhl_hyperevm_stablecoins`. See
[Daily Yields](/api/recipes/daily-yields/#daily-yields) for more information. Sunsetting by Jan 8, 2026
* `SolanaBridgeEvents` query: Migrate to `solanaSupply`. Sunsetting by Jan 8, 2026
### 2025-12-15
#### Added
* Prefixed stablecoins queries. See [Daily Yields](/api/recipes/daily-yields/) for more information
### 2025-12-08
#### Added
* `MDailyEarningsSnapshots` query to get aggregated daily earnings snapshots for the entire protocol.
### 2025-10-28
#### Added
* `usdz_mainnet` and `usdz_arbitrum` queries to get USDZ data from Ethereum and Arbitrum.
* `dfns_mainnet` query to get 0FNS data from Ethereum.
### 2025-10-06
#### Added
* `solanaUsdkSupply` query to get the daily supply of $USDK on Solana.
#### Fixed
* Wrapped $M's `accruedYield` and `unclaimedYield` values on Ethereum.
### 2025-07-07
#### Added
* $M token on-chain Arbitrum data is now available under the `arb_*` queries.
#### Added
* Changelog page
* Doc's recipe to query $M Supply snapshots
### 2025-06-24
#### Fixed
* `MHolderSnapshots`: some holders with large historical data were getting wrong values for recent dates.
### 2025-04-01
🍃 Initial release
## Authentication
The M0 Protocol API requires authentication via API keys to access its endpoint. This ensures that only authorized users can interact with the API and access its data.
[Contact us](https://www.m0.org/contact-us) to obtain an API key.
### Using API Keys
To authenticate your requests, include the API key in the `Authentication` header of your HTTP requests. For example post with curl:
```
curl -i \
--request POST \
--header "Content-Type: application/json" \
--header "Authorization: ApiKey " \
--data '{"query":"{ __typename }"}' \
https://protocol-api.m0.org/graphql
```
API keys should be kept secret and not exposed in public repositories or client-side code. If you believe your API key has been compromised, please contact us immediately to revoke the key and issue a new one.
### Storing API Keys Securely
To protect your API key and prevent unauthorized access, follow these best practices:
#### Restrict Access by Origin and IP
Configure your API key to limit where it can be used from:
* **IP Allowlisting**: Restrict the API key to specific IP addresses or CIDR ranges that correspond to your backend servers
* **Origin Restrictions**: If applicable, limit the domains or origins that can make requests using your API key
Contact us to configure these restrictions for your API key. This adds an additional layer of security, ensuring that even if your key is compromised, it cannot be used from unauthorized locations.
#### Use a Backend Service
Consider implementing a backend service that:
* Stores the API key securely in environment variables or a secrets manager
* Acts as a proxy between your frontend and the M0 Protocol API
* Handles authentication on behalf of your users
This approach ensures that your API key is never transmitted to or accessible from the client.