🪙mTokens

mTokens are ERC-20 compliant tokens that represent positions within the Moonwell protocol. When you supply assets, you receive mTokens in return. As interest accrues to the market, the exchange rate between mTokens and the underlying asset increases - meaning your mTokens become redeemable for a growing amount of the underlying over time.

mTokens are transferrable and fungible, and can be redeemed for the underlying asset provided there is sufficient liquidity and the position has not been designated as collateral.

Each market supported by the protocol (e.g. USDC, WETH, cbBTC) is represented by its own mToken contract. The mToken is responsible for:

  • Accepting new deposits (minting mTokens)

  • Allowing withdrawals of assets from the protocol (redeeming/burning mTokens)

  • Managing borrow requests for a specific asset

  • Facilitating repayments for a specific market

  • Handling liquidations for specific market positions

  • Calculating reserve and seize shares to increase protocol reserves

Integration Quickstart

The following example demonstrates a complete supply → borrow → repay → withdraw flow. All interactions go through the mToken contracts directly. Before borrowing, you must enter the market via the Comptroller.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {MErc20} from "@moonwell/MErc20.sol";
import {EIP20Interface} from "@moonwell/EIP20Interface.sol";
import {ComptrollerInterface} from "@moonwell/ComptrollerInterface.sol";

contract MoonwellIntegration {
    /// @notice Supply USDC and borrow WETH against it
    function supplyAndBorrow(
        address mUSDC,    // Moonwell USDC mToken
        address mWETH,    // Moonwell WETH mToken
        address comptroller,
        uint256 supplyAmount,
        uint256 borrowAmount
    ) external {
        // 1. Approve the mToken to pull underlying
        EIP20Interface usdc = EIP20Interface(MErc20(mUSDC).underlying());
        usdc.approve(mUSDC, supplyAmount);

        // 2. Supply - mints mTokens to this contract
        uint err = MErc20(mUSDC).mint(supplyAmount);
        require(err == 0, "mint failed");

        // 3. Enter the market - enables USDC as collateral
        address[] memory markets = new address[](1);
        markets[0] = mUSDC;
        uint[] memory results = ComptrollerInterface(comptroller).enterMarkets(markets);
        require(results[0] == 0, "enterMarkets failed");

        // 4. Borrow WETH against USDC collateral
        err = MErc20(mWETH).borrow(borrowAmount);
        require(err == 0, "borrow failed");
    }

    /// @notice Repay WETH borrow and withdraw USDC
    function repayAndWithdraw(
        address mUSDC,
        address mWETH,
        uint256 repayAmount,
        uint256 withdrawAmount
    ) external {
        // 1. Approve and repay the borrow
        EIP20Interface weth = EIP20Interface(MErc20(mWETH).underlying());
        weth.approve(mWETH, repayAmount);

        uint err = MErc20(mWETH).repayBorrow(repayAmount);
        require(err == 0, "repay failed");

        // 2. Withdraw USDC by specifying the underlying amount
        err = MErc20(mUSDC).redeemUnderlying(withdrawAmount);
        require(err == 0, "redeem failed");
    }

    /// @notice Query current positions
    function getPositions(
        address mToken,
        address account
    ) external view returns (
        uint256 mTokenBalance,
        uint256 borrowBalance,
        uint256 exchangeRate
    ) {
        (uint err, uint bal, uint borrows, uint rate) =
            MErc20(mToken).getAccountSnapshot(account);
        require(err == 0, "snapshot failed");
        return (bal, borrows, rate);
    }
}

Key Concepts

Exchange Rate

The exchange rate determines how many underlying tokens each mToken is worth:

As interest accrues, totalBorrows grows, increasing the exchange rate. This means suppliers earn interest simply by holding mTokens - no claiming required.

Interest Accrual

Interest accrues per-second (not per-block). Rates are determined by the market's interest rate model based on utilization:

Higher utilization means higher borrow rates and higher supply rates.

Error Handling

mToken functions use two failure modes:

  • Error codes - Business logic failures (comptroller rejection, insufficient collateral, freshness checks) return a non-zero uint without reverting. Always check the return value.

  • Reverts - Underlying token transfer failures, math overflows, and Comptroller pause/cap enforcement use require() and revert the transaction.

Integrators should handle both: use try/catch for reverts, and check return values for error codes. See Contract Interactions for the full error code table.

Deployed Contracts

Base

OP Mainnet

For the complete list of all deployed contracts across all chains, see Contracts.

📝Contract Interactionschevron-right

Last updated