Multi-Collateral Option: JMIExtension
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.
- 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
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
-
Multi-Collateral Wrapping: Users can wrap either
$Mor any approved stablecoin (USDC, DAI, etc.) through the SwapFacility. The contract tracks each asset's balance separately. -
Asset Caps: Each non-
$Mcollateral has a maximum cap. This limits risk exposure to any single stablecoin. The$Mtoken has no cap and can always be wrapped. -
Yield Accrual: Only the
$Mportion of the backing earns yield. As the contract's$Mbalance grows beyond its required backing, yield becomes claimable. -
Yield Distribution: The
claimYield()function mints new JMI tokens to the designatedyieldRecipient, equal to the yield surplus. -
Selective Unwrapping: Users can only unwrap to
$M. The maximum unwrap amount is limited to the$Mbacking (total supply minus non-$Masset backing). -
Asset Replacement: The
replaceAssetWithMfunction allows swapping$Mfor any non-$Mcollateral 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 AssetsWhere:
- M Backing =
totalSupply() - totalAssets() - Total Non-M Assets = Sum of all non-
$Mcollateral (in extension decimals)
The claimable yield is calculated as:
Yield = mBalanceOf(contract) - M BackingThe 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
$Mbacking supports.
Security Considerations
-
Fee-on-Transfer Protection: The contract explicitly checks received amounts match expected amounts, reverting on discrepancies.
-
Inflation Attack Prevention: The
replaceAssetWithMfunction uses the trackedassetBalanceOfrather than actual token balance, preventing manipulation via direct token transfers. -
Access Control: All sensitive functions are protected by role-based access control.
-
Emergency Controls: The
PAUSER_ROLEcan halt operations in case of security incidents. -
Frozen Accounts: The
FREEZE_MANAGER_ROLEcan block compromised addresses from interacting with the token. -
Upgradability: The contract uses OpenZeppelin's transparent proxy pattern, allowing for future upgrades with proper governance.
Ready to build? Follow the JMI implementation guide →

