Getting started
The JMIExtension (Just Mint It) is an enhanced Treasury Model implementation that accepts multiple collateral types while still directing 100% of rewards to a single recipient.
When to Use JMI instead of standard MYieldToOne:
- Projects requiring flexibility to accept multiple stablecoins as 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 rewards are centralized but collateral sources are diversified.
Source Code: JMIExtension.sol
Key Differentiators from MYieldToOne
| Feature | MYieldToOne (Standard) | JMIExtension (JMI) |
|---|---|---|
| Collateral | Single-collateral | Multi-collateral (liquid stablecoins such as USDC, USDT also supported) |
| 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 - Rewards 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 an approved stablecoin (USDC, DAI, etc.) through the SwapFacility. The contract tracks each asset's balance separately.
- Asset Caps: Each stablecoin collateral component has a maximum cap. This limits risk exposure to any single stablecoin.
- Rewards Accrual: Only the non-stablecoin collateral portion of the reserves earns rewards.
- Rewards Distribution: The
claimYield()function mints new JMI tokens to the designatedyieldRecipient, equal to the rewards surplus. - Selective Unwrapping: Users can only unwrap to the base asset. The maximum unwrap amount is limited to the non-stablecoin collateral portion of reserves (total supply minus stablecoin asset backing).
- Asset Replacement: The
replaceAssetWithMfunction allows swapping approved collateral for approved stablecoins 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 approved stablecoin collateral (in extension decimals)
The claimable rewards are calculated as:
Rewards = mBalanceOf(contract) - M Backing
The JMI backing model assumes a 1:1 peg between all deposited assets. 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 approved stablecoin collateral asset via setAssetCap(). |
YIELD_RECIPIENT_MANAGER_ROLE | Controls where rewards are 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 an approved stablecoin collateral asset. Called via SwapFacility. |
wrap(recipient, amount) | Mints JMI tokens by depositing approved collateral (inherited from MExtension). Called via SwapFacility. |
Unwrapping Functions
| Function | Description |
|---|---|
unwrap(recipient, amount) | Burns JMI tokens and sends approved collateral to the recipient. |
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 approved collateral for approved stablecoins held by the contract. |
Rewards Functions (Inherited)
| Function | Description |
|---|---|
yield() | Returns the current claimable rewards amount. |
claimYield() | Claims rewards by minting new JMI tokens to the recipient. |
setYieldRecipient(address) | Updates the rewards 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 approved stablecoin backing (in extension decimals) |
isAllowedAsset(asset) | bool | True if asset is approved collateral 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 |
YieldClaimed | amount | Rewards claimed |
YieldRecipientSet | yieldRecipient | Rewards 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 approved collateral backing for unwrap |
ZeroAssetCapManager() | Asset cap manager is zero address |
ZeroAdmin() | Admin is zero address |
ZeroYieldRecipient() | Rewards recipient is zero address |
ZeroYieldRecipientManager() | Rewards 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 Approved Collateral Backing: Users cannot unwrap more JMI than the contract's approved collateral backing 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
Implementation Guide: JMI (Offshore)
Step-by-step instructions for deploying a JMI (Just Mint It) stablecoin extension with multi-collateral support and centralized rewards distribution.
Getting started
Deep dive into the Treasury Model template where all accrued rewards are captured by the use case owner.