# Request an API key Request an API key for enterprise access to the Morpho API. Source: https://docs.morpho.org/request-api-key Enterprise access serves the private API at `https://private.api.morpho.org`, authenticated with your API key in the `x-api-key` header. Public endpoints on `https://api.morpho.org` stay keyless under standard rate limits. Fill in the form below and our team will follow up by email. --- # Morpho Curation Source: https://docs.morpho.org/curate
This section serves as the definitive resource for Morpho Vault curators. It provides essential information to create, manage, and optimize vaults within the Morpho ecosystem. ## Introduction to Curation [#introduction-to-curation] Curators configure vault parameters and execute liquidity allocation. This guide highlights the core aspects of effective vault curation. ## Curator Responsibilities [#curator-responsibilities] ### Borrower Needs [#borrower-needs] * **Competitive Rates:** Access to markets offering attractive and competitive rates * **Liquidity Assurance:** Reliable liquidity during market stress to prevent rate spikes * **Capital Allocation:** Adequate capital support as positions grow * **Responsive Curation:** Quick adaptations to changing market conditions ### Depositor Needs [#depositor-needs] * **Risk Mitigation:** Zero tolerance for bad debt events * **Competitive Yields:** APYs that align with risk profiles * **Security & Monitoring:** Robust security measures and continuous collateral liquidity oversight * **Transparency:** Clear and ongoing risk assessments ## Operational Requirements [#operational-requirements] ### Admin Tasks [#admin-tasks] * **Role & Security Configuration:** Establish and maintain secure role assignments * **Market Listings:** Create and manage market listings effectively * **Fee & Cap Adjustments:** Update vault fee parameters and adjust allocation caps * **Shared Liquidity:** Use the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/) for current Public Allocator integration ### Market Curation [#market-curation] * **Liquidity Reallocation:** Automate transfers between markets to optimize capital efficiency * **Performance Monitoring:** Track vault APYs against target utilization parameters * **Risk Assessment:** Evaluate liquidity risks, oracle reliability, collateral fundamentals, chain-specific factors, and multisig security * **Health Tracking:** Continuously monitor borrower conditions and optionaly execute liquidation processes for at-risk positions ## Reward Mechanisms [#reward-mechanisms] * **MORPHO Rewards:** Distributed to whitelisted markets and, in part, passed directly to vault depositors * **Oracle Integration:** Utilizes onchain price feeds from providers such as Chainlink, Api3, Redstone, Pendle, Hashnote, Chronicle, and Pyth * **Blockchain-Specific Deployment:** Rewards distribution varies by blockchain, with official deployments receiving priority * **Additional Configurations:** Extra market-level or vault-level rewards can be set up following the [reward programs guide](https://github.com/morpho-org/morpho-blue-reward-programs?tab=readme-ov-file#create-a-market-program) ## Public Allocator Integration [#public-allocator-integration] For new shared-liquidity integrations, use the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). It covers the current Vault V2 workflow without restoring retired Vault V1 mechanics. ## Key Advantages for Curators [#key-advantages-for-curators] * **Configurable Liquidity Efficiency:** Benefit from advanced market-matching algorithms * **Granular Risk Control:** Configure precise market exposure parameters * **Non-Custodial Security:** Ensure transparent, immutable vault operations * **Dynamic Reallocation:** Maintain competitive rates with automated reallocation tools * **Flexible Customization:** Tailor vault parameters to meet diverse market needs ## Get Started [#get-started] New curators should begin with this overview to build a solid foundation, while experienced curators may reference specific sections for targeted guidance. This concise document is designed to enhance vault performance, assess risk effectively, and configure vault parameters for all vault users. Refer to [this section](/get-started/resources/contracts/) to dig into the specs of all smart contracts involved. --- # Morpho Tool Suite Source: https://docs.morpho.org/curate/tool-suite This section details the critical applications developed by the Morpho Association for effective vault management and ecosystem participation. Each application serves specific functions in the curation workflow. ## Prime Apps [#prime-apps] ### Curator App V2 [#curator-app-v2] [curator.morpho.org](https://curator.morpho.org/vaults) **Purpose:** Delivers specialized tools for Morpho Vault V2 curators to configure vault operations. **Key Features:** * Create and deploy new vaults V2 * Curate adapter listings and parameters * Configure vault settings and permissions * Set allocation caps, strategies and timelocks **Version:** Beta deployment with ongoing feature development **Implementation Note:** For actions not yet implemented in the Curator App, use direct interaction with vault contracts through verified sources such as Etherscan. Always verify contract addresses before interaction. See curate section [here](/curate/tutorials-v2/vault-creation/). ### Curator App V1 [#curator-app-v1] [curator-v1.morpho.org](https://curator-v1.morpho.org/) **Purpose:** Delivers specialized tools for legacy Morpho Vault V1 curators to configure vault operations. **Key Features:** * Create and deploy new vaults V1 * Curate market listings and parameters * Configure vault settings and permissions * Set allocation caps, strategies and timelocks **Version:** Beta deployment with ongoing feature development **Implementation Note:** For actions not yet implemented in the Curator App, use direct interaction with vault contracts through verified sources such as Etherscan. Always verify contract addresses before interaction. See curate section [here](/curate/tutorials-v1/vault-creation/). ## Utility Apps [#utility-apps] ### Oracle Decoder [#oracle-decoder] [oracles.morpho.dev](https://oracles.morpho.dev/oracle-decoder) **Purpose:** Aid oracle configurations to ensure price feed accuracy and security. **Key Features:** * Decode oracle configurations * Verify price accuracy against reference sources * Test oracle configurations * Identify configuration issues **Version:** Production **Implementation Note:** Always verify oracle data configurations before deploying to production environments. Use this tool to: 1. Confirm correct asset pricing 2. Validate operational parameters 3. Test edge case scenarios ### Liquidation App [#liquidation-app] [liquidation.morpho.org](https://liquidation.morpho.org/) **Purpose:** Provides manual liquidation capabilities for unhealthy positions. **Key Features:** * Identify liquidatable positions * Execute manual liquidations * Calculate liquidation profitability * Monitor position health metrics **Version:** Production and requires supplemental automation **Implementation Note:** Develop and deploy automated liquidation bots for production environments. The liquidation app serves primarily as a backup mechanism when bots fail or for testing purposes. ## Consumer Apps [#consumer-apps] ### Main App [#main-app] [app.morpho.org](https://app.morpho.org/) **Purpose:** Interface for both lenders interacting with Morpho vaults and Borrowers interacting with Morpho Markets. **Key Features:** * View all Morpho Markets & Morpho Vaults * Deposit, manage, and exit positions in Morpho Vaults * Enter, manage, and exit from borrow positions * Track vault performance metrics * Monitor position health and market statistics **Version: Production** ### Fallback App [#fallback-app] [fallback.morpho.org/](https://fallback.morpho.org/) **Purpose:** Built for emergencies, this app prioritizes resilience over speed. This interface is for both lenders interacting with Morpho vaults and Borrowers interacting with Morpho Markets. **Key Features:** * View your Morpho Markets & Morpho Vaults positions * Exit positions from Morpho Vaults and Morpho Markets **Version: Production** ### Data Dashboard [#data-dashboard] [data.morpho.org](https://data.morpho.org/) **Purpose:** Provides on-chain data and analytics for the Morpho ecosystem. **Version:** Production ## External Apps - Rewards [#external-apps---rewards] Rewards on Morpho are distributed via the Merkl stack. One can create rewards programs, monitor rewards programs details at protocol level or for a specific user, and claim rewards via the Merkl app. This external application is provided by [Merkl](https://x.com/merkl_xyz). The Morpho Association does not endorse or guarantee its functionality. Use at your own discretion. * [campaigns.morpho.org](https://campaigns.morpho.org/) ## Bots and automations [#bots-and-automations] ### Liquidation bot [#liquidation-bot] [morpho-blue-liquidation-bot](https://github.com/morpho-org/morpho-blue-liquidation-bot) **Purpose:** A simple, fast, and easily deployable liquidation bot for the Morpho V1 protocol. This bot is entirely based on RPC calls and is designed to be easy to configure, customizable, and ready to deploy on any EVM-compatible chain. **Key Features:** * Automatically detects liquidatable positions and executes the liquidations. * Multi-chain compatible. * Configurable liquidity venues. * Profit evaluation thanks to configurable pricers. * Minimal setup and dependencies (RPC-only, no extra infra required). ### Vault V2 Reallocation bot [#vault-v2-reallocation-bot] [vault-v2-reallocation-bot](https://github.com/morpho-org/vault-v2-reallocation-bot) **Purpose:** A simple, fast, and easily deployable reallocation bot for the Morpho Vaults V2. This bot is entirely based on RPC calls and the Morpho API and is designed to automate Morpho Vaults V2 reallocations according customable strategies. **Key Features:** * Automatically rebalances assets within Morpho Vaults V2 to maintain capital efficiency * Equalizes utilization rates across markets * Multi-chain compatible (Ethereum, Base, and more) * Configurable minimum threshold for utilization changes (2.5% by default) This bots are provided as-is, without any warranty. The Morpho Association is not responsible for any potential loss of assets resulting from the use of this bot, including (but not limited to) gas fees, failed transactions, liquidations or reallocations on malicious or misconfigured markets. --- # Developers Build on Morpho with product integrations, data tools, and open-source examples. Source: https://docs.morpho.org/developers Developers Vault integration Yield-generating deposits Market integration Collateralized borrowing GraphQL REST Typed libraries Transaction tooling Public dashboards Shared analytics On-demand liquidity Vault liquidity management Oracle implementations Adapters Bot implementations Liquidation tooling Code Front end Minimal-resource interface Variable-rate markets Ponder template The Morpho Subgraphs ([`morpho-org/morpho-blue-subgraph`](https://github.com/morpho-org/morpho-blue-subgraph)) are deprecated. The repository has not been maintained since March 2025 and the Morpho Association no longer provides official support. The source remains open-source and can be forked or self-deployed, but should no longer be considered an official Morpho data source. For indexed onchain Morpho data, use the [Morpho API](/developers/api/get-started/). --- # Customer Stories Powering builders of tomorrow's finance. Source: https://docs.morpho.org/get-started/customer-stories Customer Stories {/* Horizontal CTA card - mirrors the Morpho Agents card pattern in get-started/index.mdx */} {/* Three story cards aligned on one baseline */} --- # Get Started Source: https://docs.morpho.org/get-started {/* Main container with 20px top margin */} {/* Docs introduction */} Morpho Docs {/* Use cases */} {/* For app builders */} Integrate Earn Integrate Borrow {/* For vault curators */} Create a vault Setup vault roles {/* For AI agents */} Use Morpho MCP Integrate with Morpho Skills {/* For protocol developers */} Explore contracts {/* Integration methods */} {/* API */} {/* Typescript SDK */} --- # Products Build powerful financial applications with Morpho's decentralized lending infrastructure. Source: https://docs.morpho.org/get-started/products Products Simple Configurable Matching your risk appetite Non-custodial Low costs Higher collateralization factors Per market rates Zero fees --- # Morpho Overview Source: https://docs.morpho.org/learn ## Introduction [#introduction] **Morpho** is a decentralized lending protocol with different entities and individuals contributing to its development and adoption. As a result, the documentation refers to different areas of “Morpho” which are worth distinguishing. * **The Morpho Protocols**: Decentralized, noncustodial lending protocols implemented for the Ethereum Virtual Machine. * **The Morpho Interfaces**:\ Multiple web interfaces allowing easy interaction with the Morpho protocol. Those interfaces are ways to interact with the Morpho protocol. * **Morpho Governance**:\ A governance system for governing the Morpho Protocol, enabled by the [MORPHO token](/learn/governance/morpho-token/#1). * **Morpho Association**:\ A France-registered association that regroups the main contributors to promote the development and the decentralization of the Morpho Protocol. [The Morpho Association](/learn/governance/organization/#morpho-association) hosts one of the Morpho Interfaces. ## Morpho [#morpho] Morpho is a simple lending primitive layer that allows the creation of immutable and efficient lending markets in a permissionless way. The protocol comes with EVM smart contracts which facilitate interactions and integrations. ### Overview [#overview] Morpho is a decentralized protocol enabling the overcollateralized lending and borrowing of crypto assets (ERC20 & ERC4626 Tokens) on the [Ethereum Virtual Machine](https://ethereum.org/en/developers/docs/evm/). The protocol is implemented as an immutable smart contract, engineered to serve as a trustless base layer for lenders, borrowers, and applications. Morpho is licensed under a dual license (BUSL-1.1 and GPLv2) which you can find [here](https://github.com/morpho-org/morpho-blue/blob/main/LICENSE). Once deployed, Morpho will function in perpetuity, provided by the existence of the blockchain. ### Key Concepts [#key-concepts] Overcollateralized lending in Morpho involves: * **Collateralization**: Users provide collateral to borrow other assets * **Risk Protection**: Liquidation mechanisms protect the protocol through loan-to-value ratios * **Interest Accrual**: Dynamic interest rates based on market conditions * **Open Participation**: Anyone can lend or borrow through the protocol * **Non-custodial Design**: Users maintain ownership of their assets at all times ## Blue and Midnight [#blue-and-midnight] Morpho has two lending protocols: Variable Rate Markets (Blue) and Fixed Rate Markets (Midnight). They coexist and are complementary.
--- # Get block timestamp Source: https://docs.morpho.org/api/blocks/get-block-timestamp {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get chains Source: https://docs.morpho.org/api/chains/get-chains {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get token price Source: https://docs.morpho.org/api/global/get-token-price {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get token Source: https://docs.morpho.org/api/global/get-token {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market APY averages Source: https://docs.morpho.org/api/markets/get-market-apy-averages {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market APY history Source: https://docs.morpho.org/api/markets/get-market-apy-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market liquidity Source: https://docs.morpho.org/api/markets/get-market-liquidity {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market state history Source: https://docs.morpho.org/api/markets/get-market-state-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market state Source: https://docs.morpho.org/api/markets/get-market-state {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market Source: https://docs.morpho.org/api/markets/get-market {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List markets Source: https://docs.morpho.org/api/markets/list-markets {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User market position history Source: https://docs.morpho.org/api/markets/user-market-position-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User market position performance Source: https://docs.morpho.org/api/markets/user-market-position-performance {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User market position Source: https://docs.morpho.org/api/markets/user-market-position {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get oracle state Source: https://docs.morpho.org/api/oracles/get-oracle-state {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault allocation history Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-allocation-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault allocations Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-allocations {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault APY history Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-apy-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault pending governance actions Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-pending-governance-actions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault realized average APY Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-realized-average-apy {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault state history Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-state-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault state Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault-state {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V1 vault Source: https://docs.morpho.org/api/vaults-v1/get-v1-vault {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List user V1 vault positions Source: https://docs.morpho.org/api/vaults-v1/list-user-v1-vault-positions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List V1 vaults Source: https://docs.morpho.org/api/vaults-v1/list-v1-vaults {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V1 vault position activity Source: https://docs.morpho.org/api/vaults-v1/user-v1-vault-position-activity {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V1 vault position history Source: https://docs.morpho.org/api/vaults-v1/user-v1-vault-position-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V1 vault position performance Source: https://docs.morpho.org/api/vaults-v1/user-v1-vault-position-performance {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V1 vault position Source: https://docs.morpho.org/api/vaults-v1/user-v1-vault-position {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault allocations Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-allocations {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault APY history Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-apy-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault caps history Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-caps-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault pending governance actions Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-pending-governance-actions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault realized average APY Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-realized-average-apy {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault state history Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-state-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault state Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-state {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault withdrawal options Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault-withdrawal-options {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get V2 vault Source: https://docs.morpho.org/api/vaults-v2/get-v2-vault {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List user V2 vault positions Source: https://docs.morpho.org/api/vaults-v2/list-user-v2-vault-positions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List V2 vaults Source: https://docs.morpho.org/api/vaults-v2/list-v2-vaults {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V2 vault position activity Source: https://docs.morpho.org/api/vaults-v2/user-v2-vault-position-activity {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V2 vault position history Source: https://docs.morpho.org/api/vaults-v2/user-v2-vault-position-history {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V2 vault position performance Source: https://docs.morpho.org/api/vaults-v2/user-v2-vault-position-performance {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # User V2 vault position Source: https://docs.morpho.org/api/vaults-v2/user-v2-vault-position {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Adapter Registry Source: https://docs.morpho.org/curate/concepts/adapter-registry Vault V2 introduces the concept of an **Adapter Registry** - a [smart contract](/get-started/resources/addresses/#morpho-v2-contracts) that defines which adapters a vault can use to allocate assets. ## Why Registries Matter [#why-registries-matter] [Vault V2](https://github.com/morpho-org/vault-v2) is future-proof: any curator can add adapters to deposit assets into external protocols. This flexibility is powerful but also introduces risk: a vault could attract liquidity using the Morpho ecosystem and later redirect assets to other protocols that may be less secure or less aligned with Morpho’s safety and quality standards. Adapter Registries solve this by giving curators (and end users) a way to lock the vault’s allocation perimeter. * Without a registry → any adapter can be added. * With a registry → only adapters explicitly approved by that registry can be used. * The curator can abdicate the `setAdapterRegistry` function (by timelocking it forever) to make this restriction permanent and noncustodial. For implementation details, see the [vault-V2-adapter-registries repository](https://github.com/morpho-org/vault-v2-adapter-registries). ## The Morpho Registry [#the-morpho-registry] The **Morpho Registry** is an official registry maintained by the Morpho governance. It lists adapters approved by the Morpho governance - including Morpho Markets (V1, V2) and Morpho Vaults V1 - and allows new ones to be added over time. Vaults that: 1. Set their adapter registry to the Morpho Registry, and 2. Abdicate the ability to change it are considered Morpho Vaults V2. These Morpho Vaults V2 gain access to Morpho ecosystem benefits, such as: * Eligibility for display and distribution in the Morpho App. * Access to Morpho’s support, incentive programs and Prime apps suite. * Confidence for depositors that the vault will remain within the Morpho-approved adapter perimeter. Developers can explore the Morpho Registry contract and other registry implementations in the [vault-V2-adapter-registries GitHub repo](https://github.com/morpho-org/vault-v2-adapter-registries). ## Key Properties [#key-properties] **Future proof**: vaults can add a new adapter, benefiting from it while preserving the liquidity the vault has built. **Add-only**: Once an adapter is approved, it stays; adapters can’t be removed to avoid breaking existing vaults. **Non-custodial guarantees**: Users can always exit before any configuration change (thanks to timelocks and in-kind redemptions). **Composable**: Builders can create their own registries if they want different adapter rules. --- # Fees Source: https://docs.morpho.org/curate/concepts/fee Morpho Vaults include fee mechanisms that allow vaults to collect fees from interest accruing within vault smart contracts. The implementation and capabilities of these fees have evolved significantly from Vaults V1 to Vaults V2, offering curators more flexibility in designing their business models. ## Fee Structure in Morpho Vaults V2 [#fee-structure-in-morpho-vaults-v2] Vaults V2 introduce a sophisticated, dual-fee structure, giving curators precise control over revenue streams. All fee-related changes are managed by the `Curator` and are subject to a **timelock**, ensuring transparency and giving users time to react to proposed changes. ### 1. Performance Fee [#1-performance-fee] The performance fee is a percentage of the yield generated by the vault, aligning the curator's revenue with the vault's success. * **Fee Basis**: Charged as a percentage of the interest generated by the vault's allocated assets. This is automatically calculated based on the real asset values reported by adapters. * **Maximum Rate**: Capped at **50%** of the generated interest. * **Control**: The `Curator` sets the fee rate and the recipient address. ### 2. Management Fee [#2-management-fee] The management fee is a continuous fee charged on the vault's total deposited assets, providing a fee for the vault's curator. * **Fee Basis**: Calculated as an annualized percentage of the vault's `totalAssets`. A small portion of this fee is collected each time interest is accrued. * **Maximum Rate**: Capped at **5% per year**. * **Control**: The `Curator` sets the fee rate and the recipient address. ### Fee Collection Mechanism in V2 [#fee-collection-mechanism-in-v2] Fees in Vaults V2 are collected seamlessly without affecting depositors' principal. 1. An interaction (like a deposit, withdrawal, or a direct `accrueInterest` call) triggers interest and fee calculations. 2. The vault aggregates asset values from all adapters to determine the total interest generated. 3. The performance and management fees are calculated based on this interest and the total assets. 4. New vault shares corresponding to the fee amounts are minted directly to the `performanceFeeRecipient` and `managementFeeRecipient` addresses. 5. The vault's `totalAssets` are updated to reflect the new state after interest distribution. This share-minting mechanism ensures that fees are taken from the profits, while the principal of all depositors remains fully intact. ## Fee Structure in Morpho Vaults V1 [#fee-structure-in-morpho-vaults-v1] The fee system in Vaults V1 is simpler, consisting of a single performance-based fee controlled by the `Owner`. * **Fee Type**: A single **performance fee**. There is no management fee. * **Fee Basis**: The fee is a percentage of the **actual interest** accrued onchain from the underlying variable rate market positions. * **Control**: The `Owner` of the vault sets the fee rate and the recipient. * **Security**: Fee parameter changes are **immediate** and do not have a timelock. * **Maximum Rate**: Capped at **50%** of the generated interest. * **Collection**: Like in V2, fees are collected by minting new vault shares to the `feeRecipient`. ## Comparison: Vaults V2 vs. Vaults V1 [#comparison-vaults-v2-vs-vaults-v1] | Feature | Morpho Vaults V2 | Morpho Vaults V1 (MetaMorpho) | | ------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------- | | **Fee Types** | **Performance Fee** + **Management Fee** | Performance Fee Only | | **Performance Fee Basis** | Interest automatically calculated from adapter asset values | **Actual onchain interest** from variable rate market positions | | **Management Fee Basis** | Percentage of **Total Deposited Assets** | N/A | ## Fee Recipient Requirements [#fee-recipient-requirements] **The fee recipient wallet must be able to interact with the vault to actually receive the underlying asset**. Fees are always minted as shares, and converting them to the underlying asset requires calling `redeem()`. **Two options:** * Fee recipient calls `redeem()` directly, they periodically withdraw their accumulated fee shares. * Fee recipient grants allowance once, then someone else redeems on their behalf, the fee recipient does a single `approve(claimerAddress, type(uint256).max)` (approval on the vault receipt token, not the underlying asset), then another address can call `redeem(shares, feeRecipient, feeRecipient)` on an ongoing basis. Either way, the fee recipient must be capable of interacting with the vault (EOA, smart wallet, multisig, etc.). A fully passive address that never interacts with the chain won't be able to actually receive fees in the underlying asset. --- # Gates in Morpho Vaults Source: https://docs.morpho.org/curate/concepts/gates Morpho Vaults (V1 and V2) support different mechanisms to restrict access to deposits, withdrawals, and transfers. Vault V2 introduces **first-class gates** as external contracts with timelock and abdication guarantees, while Vault V1.1 achieves gating operationally by manipulating the `supplyQueue` and market caps. ## High-Level Comparison [#high-level-comparison] | Aspect | Vault V1.1 | Vault V2 | | ---------------------- | ----------------------------------------- | ---------------------------------------------------------------- | | **Native Gates** | ❌ None (must use supplyQueue + caps) | ✅ Four explicit gates (receive/send shares, receive/send assets) | | **Granularity** | Coarse: deposits only (via `supplyQueue`) | Fine: deposits, withdrawals, transfers, fee accrual | | **Timelock** | Caps increases are timelocked (24h–2w) | All gate changes timelocked (configurable, e.g. up to 3 weeks) | | **Abdication** | ❌ Not supported | ✅ Supported (permanent permissionless or permanently gated) | | **Atomic batching** | ✅ `multicall` allows open–deposit–close | ✅ `multicall` supported but rarely needed for gates | | **Intended use cases** | Simple whitelisting of suppliers | Compliance, allowlists, permissioned transfers | ## Gates in Vault V2 [#gates-in-vault-v2] ### Executive Summary [#executive-summary] Gates in Vault V2 are external smart contracts that provide **access control and compliance mechanisms** for vault operations. They enable selective permissions for vault receipt token transfers, asset deposits, and asset withdrawals while making it possible to maintain the vault's noncustodial guarantees. Gates are **optional** (if not set, operations are unrestricted) and can be **timelocked** for security. ### Core Architecture [#core-architecture] #### Four Gate Types [#four-gate-types]
Vault V2 implements a **four-gate system** addressing different operational vectors: #### 1. **Receive Shares Gate** (`receiveSharesGate`) [#1-receive-shares-gate-receivesharesgate] * **Interface**: `IReceiveSharesGate` with `canReceiveShares(address)` * **Controls**: Who can receive vault shares * **Operations**: * `deposit()`, `mint()` → checks `canReceiveShares(onBehalf)` * `transfer()`, `transferFrom()` → checks `canReceiveShares(to)` * Performance/management fee accrual → checks `canReceiveShares(feeRecipient)` * **⚠️ Critical Risk**: Can prevent depositors from getting back shares deposited on other contracts. Note that if the gate **reverts** instead of returning `false`, it can cause a **denial of service (DoS)** across core vault operations. #### 2. **Send Shares Gate** (`sendSharesGate`) [#2-send-shares-gate-sendsharesgate] * **Interface**: `ISendSharesGate` with `canSendShares(address)` * **Controls**: Who can send vault shares * **Operations**: * `withdraw()`, `redeem()` → checks `canSendShares(onBehalf)` * `transfer()`, `transferFrom()` → checks `canSendShares(from)` * **⚠️ Critical Risk**: Can lock users out of exiting the vault or prevent depositors from getting back shares deposited on other contracts #### 3. **Receive Assets Gate** (`receiveAssetsGate`) [#3-receive-assets-gate-receiveassetsgate] * **Interface**: `IReceiveAssetsGate` with `canReceiveAssets(address)` * **Controls**: Who can receive underlying assets from the vault * **Operations**: * `withdraw()`, `redeem()` → checks `canReceiveAssets(receiver)` * **Special Rule**: Vault itself (`address(this)`) always bypasses this gate * **⚠️ Critical Risk**: Can prevent people from receiving their assets upon withdrawals #### 4. **Send Assets Gate** (`sendAssetsGate`) [#4-send-assets-gate-sendassetsgate] * **Interface**: `ISendAssetsGate` with `canSendAssets(address)` * **Controls**: Who can deposit underlying assets into the vault * **Operations**: * `deposit()`, `mint()` → checks `canSendAssets(msg.sender)` * **Non-critical**: Cannot block users' assets, while still being able to gate supplies ### Technical Implementation Deep Dive [#technical-implementation-deep-dive] Permissioned token functions (gates) can be found in the Vault V2 repository [here](https://github.com/morpho-org/vault-v2/blob/0888120e4c041690a040635dc4d448b7cb597965/src/VaultV2.sol#L910-L926) and IGate interfaces [here](https://github.com/morpho-org/vault-v2/blob/main/src/interfaces/IGate.sol). #### Gate Checking Logic [#gate-checking-logic] ```solidity // From VaultV2.sol - Gate validation functions function canReceiveShares(address account) public view returns (bool) { return receiveSharesGate == address(0) || IReceiveSharesGate(receiveSharesGate).canReceiveShares(account); } function canSendShares(address account) public view returns (bool) { return sendSharesGate == address(0) || ISendSharesGate(sendSharesGate).canSendShares(account); } function canReceiveAssets(address account) public view returns (bool) { return account == address(this) || receiveAssetsGate == address(0) || IReceiveAssetsGate(receiveAssetsGate).canReceiveAssets(account); } function canSendAssets(address account) public view returns (bool) { return sendAssetsGate == address(0) || ISendAssetsGate(sendAssetsGate).canSendAssets(account); } ``` #### Critical Implementation Requirements [#critical-implementation-requirements] Gates MUST be designed knowing it should never revert, returning always boolean values. Also gates should not use a malicious amount of gas (like something close to the block size). ### Governance and Security Model [#governance-and-security-model] #### Timelock Protection [#timelock-protection] Gates follow the vault's timelock system: ```solidity // Curator functions for gate management (timelockable) function setReceiveSharesGate(address newReceiveSharesGate) external { timelocked(); receiveSharesGate = newReceiveSharesGate; emit EventsLib.SetReceiveSharesGate(newReceiveSharesGate); } function setSendSharesGate(address newSendSharesGate) external { timelocked(); sendSharesGate = newSendSharesGate; emit EventsLib.SetSendSharesGate(newSendSharesGate); } function setReceiveAssetsGate(address newReceiveAssetsGate) external { timelocked(); receiveAssetsGate = newReceiveAssetsGate; emit EventsLib.SetReceiveAssetsGate(newReceiveAssetsGate); } function setSendAssetsGate(address newSendAssetsGate) external { timelocked(); sendAssetsGate = newSendAssetsGate; emit EventsLib.SetSendAssetsGate(newSendAssetsGate); } ``` #### The Two Types of Permanent Guarantees with Gate Abdication [#the-two-types-of-permanent-guarantees-with-gate-abdication] ```solidity // Example: Permanently disable receive shares gate changes vault.abdicate(IVaultV2.setReceiveSharesGate.selector); ``` The `abdicate()` function allows a curator to make an irreversible commitment regarding a vault's configuration, providing users with permanent guarantees. When applied to gates, this creates two distinct and powerful outcomes: ensuring a vault is either permanently permissionless or permanently governed by a fixed set of rules: #### Type 1. The Permanently Ungated Vault (Abdicating to Zero) [#type-1-the-permanently-ungated-vault-abdicating-to-zero] This strategy provides the strongest guarantee of permissionless access. * **Goal:** To ensure a vault can never have a specific type of gate imposed on it. * **How it Works:** The curator first ensures the target gate address is set to address(0) (disabling it). They then call `abdicate` on that gate's setter function (e.g., setReceiveSharesGate). Note that `abdicate` is a timelocked function, so it must be submitted and then executed after the timelock period (or immediately if the timelock is 0). * **Result:** This action permanently locks the gate address to address(0). No one can ever set a gate contract for that function in the future. This is a powerful tool for creating truly decentralized vaults where users are assured that access rules will never be introduced. #### Type 2. The Permanently Gated Vault (Abdicating to a Non-Zero Gate) [#type-2-the-permanently-gated-vault-abdicating-to-a-non-zero-gate] This strategy is designed for vaults that must adhere to a fixed, unchangeable set of compliance rules. * **Goal:** To lock a vault into a specific, non-modifiable access control policy. * **How it Works:** The curator sets the gate to the address of a deployed gate contract and then calls `abdicate` on the setter function. * **Result:** The vault becomes permanently bound to that gate contract. The rules defined within that contract can never be altered by changing the gate address. **Crucial Security Consideration:** This guarantee is only meaningful if the gate contract itself has **immutable behavior**. If the gate contract contains administrative functions that allow an owner to modify its rules (e.g., change a whitelist), then abdicating simply transfers the power to control access from the vault's curator to the gate's administrator. For a true permanent guarantee, the gate contract must be designed without such administrative controls, or its ownership must be renounced. ### Special Cases and Edge Conditions [#special-cases-and-edge-conditions] #### Vault Self-Operations [#vault-self-operations] The vault itself (`address(this)`) is **always allowed** to receive assets, regardless of the `receiveAssetsGate` configuration. This prevents the vault from blocking its own internal operations: ```solidity function canReceiveAssets(address account) public view returns (bool) { return account == address(this) || // Vault always bypasses receiveAssetsGate == address(0) || IReceiveAssetsGate(receiveAssetsGate).canReceiveAssets(account); } ``` #### Fee Recipient Gate Checks [#fee-recipient-gate-checks] **Important**: If a gate is set and reverts for fee recipients when fees are non-zero, interest accrual will revert. This makes proper gate configuration critical for vault operation. ### Impact on Non-Custodial Guarantees [#impact-on-non-custodial-guarantees] #### Force Deallocate Mechanism [#force-deallocate-mechanism] ```solidity function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf) external returns (uint256) ``` This function allows users to perform in-kind redemptions by: 1. Flashloaning liquidity 2. Supplying to an adapter's market 3. Withdrawing liquidity through `forceDeallocate` 4. Repaying the flashloan More details are provided [here](/learn/concepts/vault-v2/#are-morpho-vaults-v2-non-custodial). ### Testing Considerations [#testing-considerations] The codebase includes comprehensive gate testing: * `GateExampleTest.sol` - Tests the reference implementation (Disclaimer: this should not be used in production as is). * `GatingTest.sol` - Tests all gate integration points * Tests verify proper error handling for each gate type * Tests confirm vault self-operations bypass receive assets gate ## Gates in Vault V1 [#gates-in-vault-v1] Vault V1.1 does not include first-class gates. Instead, access control can be emulated by **playing with the `supplyQueue` and market caps**, combined with `multicall`. This provides a practical gating mechanism for suppliers. ### Key Mechanics [#key-mechanics] * **Deposits only work if the `supplyQueue` is non-empty**. If empty, `deposit` reverts and the vault is effectively closed. * **Allocator role** can update the queue at any time (no timelock). * A market must have a **non-zero cap** to appear in the queue. * **Cap increases** are timelocked (24h–2w), but **cap decreases** are instant. * The vault supports **`multicall`**, allowing atomic open–deposit–close flows to prevent front-running. ### Tutorial [#tutorial] #### One-Time Setup [#one-time-setup] 1. **Deploy** the V1.1 vault (`MetaMorphoV1_1`). 2. **Assign roles**: * Owner → multisig * Curator → multisig (or another trusted address) * Allocator → hot wallet / automation bot that will run the batch transaction 3. **Prepare the market**: ensure cap > 0 (requires timelock if raising). #### Keep the Vault Closed by Default [#keep-the-vault-closed-by-default] ```solidity // Curator or Allocator vault.setSupplyQueue(new MarketParams); // [] ``` No one can deposit, as the queue is empty. #### Open Deposit Window (Privileged Supplier) [#open-deposit-window-privileged-supplier] Perform an **atomic batch** via `multicall`: 1. Open queue (add target market). 2. Deposit assets. 3. Close queue (reset to empty). #### Example in viem [#example-in-viem] ```ts import { createPublicClient, createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { mainnet } from "viem/chains"; import { morphoVaultAbi } from "./abi"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const client = createWalletClient({ account, chain: mainnet, transport: http(RPC_URL), }); const VAULT_ADDRESS = "0x..."; const TARGET_MARKET = "0x..."; // market id const AMOUNT = 1_000n * 10n ** 18n; const openQueueCall = { abi: morphoVaultAbi, functionName: "setSupplyQueue", args: [[TARGET_MARKET]], }; const depositCall = { abi: morphoVaultAbi, functionName: "deposit", args: [AMOUNT, account.address], }; const closeQueueCall = { abi: morphoVaultAbi, functionName: "setSupplyQueue", args: [[]], }; await client.writeContract({ address: VAULT_ADDRESS, abi: morphoVaultAbi, functionName: "multicall", args: [[ client.encodeFunctionData(openQueueCall), client.encodeFunctionData(depositCall), client.encodeFunctionData(closeQueueCall), ]], }); ``` #### Operational Caveats [#operational-caveats] * **Always batch** open–deposit–close; otherwise, others may front-run deposits. * Keep caps at **0 by default** and raise only when scheduling controlled deposits. * This mechanism gates **suppliers only**; withdrawals remain unaffected. #### Final notes on Vault V1 Gating operation: [#final-notes-on-vault-v1-gating-operation] * The **supplier must sign the batch** and be the allocator itself. * If you use a Gnosis Safe, craft a Multisend with those three calls and have the Safe execute it. --- # In-Kind Redemption Source: https://docs.morpho.org/curate/concepts/in-kind-redemption In-kind redemption is an exit path for depositors when a vault cannot return all of its underlying asset because liquidity is deployed in Morpho Blue markets and borrowed out. Instead of waiting for the curator to reallocate funds or for borrowers to repay, the depositor burns vault shares and receives the corresponding vault positions: Blue supply positions for Vault V1, or available idle assets followed by Blue supply positions for Vault V2. IKR transfers the vault's market exposure for the illiquid part of the exit; it does not create liquidity or convert that position into the underlying asset. After the exit, the user manages each Blue position independently and can withdraw the underlying asset when liquidity becomes available. The user is no longer exposed to the vault's future curation decisions or fees for that position. | Vault version | What the depositor receives | | ------------- | -------------------------------------------------------------------------------------------------- | | Vault V1 | Blue supply positions corresponding to the full asset-denominated exit amount | | Vault V2 | Available underlying idle assets, then Blue supply positions net of the force-deallocation penalty | IKR complements normal `withdraw` and `redeem`; it does not replace them. Use the normal path while underlying liquidity is available because it returns the vault asset directly and, on Vault V2, avoids the force-deallocation penalty. When Vault V2 uses IKR, it returns available idle assets first and applies the penalty only to the force-deallocated remainder. Because the selected market order determines which positions the user receives, integrations should make that outcome clear before submission. Follow [Exit an illiquid vault in kind](/developers/earn/tutorials/in-kind-redemption/) for the end-to-end integration, or use the [Morpho SDK in-kind redemption reference](/developers/sdks/morpho-sdk/vault/#in-kind-redemption) for parameters and errors. --- # Concepts Source: https://docs.morpho.org/curate/concepts ## Timelock Security [#timelock-security] Implement a progressive timelock strategy: 1. **Initial Phase:** Set minimal timelock (1-6 hours) during active configuration 2. **Stabilization Phase:** Extend to 24-48 hours after configuration complete 3. **Mature Phase:** Implement 72+ hour timelock for maximum security **Frontend Listing Requirement:** Maintain timelock ≥72 hours for Morpho frontend inclusion ## Rate Optimization Strategies [#rate-optimization-strategies] Implement these strategies to maximize vault performance: 1. **Bootstrap Strategy:** * List liquid blue-chip markets initially * Provide immediate yield for early depositors * Create flexible reallocation pathways 2. **Rate Arbitrage Techniques:** * Balance rates between markets with similar risk profiles * Maintain competitive positioning against Aave/Compound rates * Strategically allocate capital to idle market to influence rates * Use the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/) for shared-liquidity integrations 3. **Partnership Optimization:** * Coordinate with asset issuers on target rates * Implement rate controls for specific assets (e.g., 5% APY maintenance) * Develop automated monitoring for rate target adherence 4. **Market Health Curation:** * Prevent excessive low rates that create lender liquidity traps * Avoid excessive high rates that discourage borrower participation * Monitor utilization ratios and adjust allocation accordingly --- # Liquidity Curation Source: https://docs.morpho.org/curate/concepts/liquidity Liquidity Curation is the process of configuring a vault's liquidity allocation within defined risk parameters. It is a dynamic process performed by the vault's `Curator` and `Allocator`, who work together to define risk parameters and deploy assets effectively. The methodology for curation differs significantly between Morpho's vault versions: * **Vaults V2** use a powerful, future-proof framework based on **Adapters** and a granular **ID & Cap System**. * **Vaults V1** use a more direct model focused on allocating to **variable rate market** instances via **Supply and Withdraw Queues**. ## Curation in Morpho Vaults V2 [#curation-in-morpho-vaults-v2] Curation in Vaults V2 is a two-tiered process that separates high-level strategy from tactical execution. The `Curator` sets the rules of the game, and the `Allocator` plays within them. ### The Core Components of V2 Curation [#the-core-components-of-v2-curation] A V2 Curator has three primary tools to build the vault's risk framework: #### 1. Adapters: The Gateway to Protocols [#1-adapters-the-gateway-to-protocols] An **Adapter** is a smart contract that allows the vault to interact with an external protocol. The Curator's first job is to decide which protocols the vault can trust by enabling their respective adapters. This makes the vault a universal gateway, capable of allocating to: * Variable rate markets (via `MorphoMarketV1AdapterV2`) * Morpho Vault V1 (via `MorphoVaultV1Adapter`) * Future Morpho versions and any other yield-bearing protocol with an adapter. #### 2. The ID & Cap System: Granular Risk Control [#2-the-id--cap-system-granular-risk-control] This is the heart of V2 risk curation. Instead of setting a simple cap on a single market, the Curator can manage exposure across abstract **risk factors**, each identified by a unique `id`. An `id` can represent anything an adapter can define, such as: * A specific collateral asset (e.g., `stETH`) * A specific oracle (e.g., `Chainlink`) * An entire protocol (e.g., all variable rate markets) * A highly specific market configuration The Curator can apply two types of caps to any `id`: * **Absolute Cap**: A fixed maximum amount of assets (e.g., "no more than 20M USDC allocated to markets with `wBTC` as collateral"). * **Relative Cap**: A maximum percentage of the vault's total assets (e.g., "no more than 30% of the vault may be exposed to Protocol X"). This system allows for sophisticated, multi-dimensional risk policies that were impossible in V1. #### 3. The Liquidity Adapter: Managing Active Liquidity [#3-the-liquidity-adapter-managing-active-liquidity] The `Allocator` can designate one enabled adapter as the vault's **`liquidityAdapter`**. This adapter serves two critical functions: 1. **Receiving Deposits**: All new user deposits are automatically routed to this adapter, ensuring capital is immediately put to work. If a cap (absolute or relative) associated with the ids returned by the liquidity adapter on the liquidity data is reached, deposit/mint will revert. There is no fallback to idle. When caps are hit, someone with allocator/curator permissions needs to either: * Increase the caps, or * Reallocate assets to other markets, or * Remove/change the liquidity adapter 2. **Fulfilling Withdrawals**: If the vault's idle assets are insufficient to cover a user's withdrawal, assets are automatically pulled from this adapter. If there is no idle asset and no liquidity available on the liquidity adapter, the Morpho Vault V2 becomes illiquid. Someone with allocator/curator permissions needs to either: * Reallocate assets from other markets to the liquidity adapter, or * Change the liquidity adapter to a market with available liquidity The liquidity adapter is typically set to a highly liquid and safe yield source to ensure smooth vault operations. ### The Curation Workflow in Practice [#the-curation-workflow-in-practice] 1. **Strategic Curation (The `Curator`)**: * **Enables Adapters**: Decides which protocols are in-scope for the vault's strategy. * **Defines the Risk Framework**: Sets absolute and relative caps on various `ids` to control exposure. * **Appoints Allocators**: Delegates the active portfolio management to trusted entities or smart contracts. *(All these actions are timelocked for security).* 2. **Tactical Allocation (The `Allocator`)**: * **Operates Within Boundaries**: The Allocator uses `allocate` and `deallocate` to move assets between the vault's idle pool and the various enabled adapters. * **Maintains Utilization**: Routes assets to adapters to maintain utilization within curator-defined caps. * **Curates Liquidity**: They set the `liquidityAdapter` to ensure there are sufficient assets for user deposits and withdrawals. | Feature | **In Morpho Vault V2** | | :--------------------- | :------------------------------------------------------------------------- | | **Allocation Target** | Any protocol with an enabled **Adapter**. | | **Risk Control** | Granular **ID & Cap System** (absolute and relative caps on risk factors). | | **Liquidity Curation** | A dedicated **`liquidityAdapter`** for deposits and withdrawals. | ## Curation in Morpho Vaults V1 [#curation-in-morpho-vaults-v1] Curation in Vaults V1 is focused exclusively on allocating capital across **variable rate market** instances. The system is more direct, relying on queues and per-market caps. ### Strategic Market Curation [#strategic-market-curation] The `Curator`'s primary role is to select a basket of variable rate market instances that align with the vault's strategy and set a hard **supply cap** on each one. This provides a simple but effective way to limit exposure to any single market. ### The Queue System: How Capital Moves [#the-queue-system-how-capital-moves] The `Allocator` is responsible for managing two critical queues that dictate how capital flows in and out of the vault: #### Supply and Withdraw Queues [#supply-and-withdraw-queues] The supply queue determines the order in which markets receive new deposits. When a user deposits assets, the vault attempts to supply liquidity to each market in sequence until the deposit is fully allocated or all market caps are reached. The withdraw queue determines the order in which markets are tapped to fulfill user withdrawals. This queue contains all markets where the vault has a non-zero supply or a non-zero cap. ### Recommended Queue Setup [#recommended-queue-setup] **Supply Queue:** `[Idle]` **Withdraw Queue:** `[Idle, Market 1, Market 2, …]` This configuration is recommended for all Morpho Vaults V1 and V1.1 as it completely prevents any misallocation of assets through normal operations or malicious intent. This setup: * **Keeps the strategy intact**: Deposits land in idle first and withdrawals exit idle first, so routine user activity can't move assets out of your yield strategy. * **Protects APY and risk settings**: Idle acts as a neutral buffer; reallocations happen only when you decide, preventing erosion of returns or allocations to assets beyond your current selection. * **Stops accidental drift**: Even well-meaning users can shuffle money between markets if queues start with different pools. Using "Idle first" on both sides blocks that. * **Thwarts quick "hit-and-run" attacks**: Flash-loan or high-balance tricks lose their leverage because idle simply drains and refills itself, leaving active markets unchanged. ## Practical Implementation [#practical-implementation] The market management system requires continuous monitoring and adjustment: * **Data-Driven Decisions**: Base cap adjustments and queue positioning on utilization metrics, yield comparisons, and risk assessments * **Capital Flow Analysis**: Monitor deposit and withdrawal patterns to optimize queue configurations * **Risk Mitigation**: Implement gradual transitions to prevent rate spikes that could drive borrowers to competitors * **Stress Testing**: Regularly simulate extreme market conditions to ensure queue systems remain robust --- # Roles & Capabilities Source: https://docs.morpho.org/curate/concepts/roles The roles and capabilities for managing Morpho Vaults differ significantly between the future-proof Vaults V2 and the original variable rate market-focused Vaults V1. This document outlines the responsibilities and permissions for each version. ## Morpho Vaults V2 Roles [#morpho-vaults-v2-roles] The Vault V2 architecture introduces a refined role system with a clear separation of concerns, designed for enhanced security and operational flexibility. **Separation of Concerns is Key in V2** Unlike V1, the `Owner` in V2 does not inherit the powers of other roles. Each role is distinct, ensuring that administrative control is separate from strategic risk curation and daily portfolio operations. ### Owner [#owner] The `Owner` holds the highest-level administrative power, responsible for appointing and managing the key roles that govern the vault. * **Recommended Approach:** A secure multisig (e.g., 4-of-6) or an equivalent institutional-grade MPC wallet. A single EOA is highly discouraged for production vaults. * **Impact of Compromise:** Full administrative control loss. The `Owner` can replace the `Curator`, who controls the vault's strategy. #### Capabilities [#capabilities] * Only one address can have this role. * Transfer ownership of the vault (`setOwner`). * Appoint or change the `Curator` (`setCurator`). * Add or remove `Sentinels` (`setIsSentinel`). * Set the vault's ERC-20 `name` and `symbol`. ### Curator [#curator] The `Curator` is the vault's chief risk officer and strategist. They define the allocation strategy, set risk parameters, and enable the tools for the `Allocator`. * **Recommended Approach:** A multisig (e.g., 2-of-4 or 3-of-5) or an MPC wallet. * **Impact of Compromise:** Can propose and, after a timelock, execute changes that alter the vault's risk profile. The `Sentinel` and depositors have the timelock duration to react. #### Capabilities [#capabilities-1] * Only one address can have this role. * **Protocol Management**: * `[Timelocked]` Enable or disable yield sources via Adapters (`addAdapter`, `removeAdapter`). * **Risk Curation**: * `[Timelocked]` Increase absolute or relative caps for any risk `id` (`increaseAbsoluteCap`, `increaseRelativeCap`). * Instantly decrease absolute or relative caps (`decreaseAbsoluteCap`, `decreaseRelativeCap`). * **Yield & Fee Management**: * `[Timelocked]` Set the `performanceFee` and `managementFee`. * `[Timelocked]` Set the `performanceFeeRecipient` and `managementFeeRecipient`. * **Operational Management**: * `[Timelocked]` Add or remove `Allocators` (`setIsAllocator`). * `[Timelocked]` Set `Gate` contracts for onchain compliance (`setSharesGate`, `setReceiveAssetsGate`, `setSendAssetsGate`). * `[Timelocked]` Set the penalty for `forceDeallocate` (`setForceDeallocatePenalty`). A `forceDeallocatePenalty` of 0 allows anyone to call `forceDeallocate` on behalf of any user. It would not lead to loss of assets, but could be used to cause vault assets to become idle at times unintended by the `Curator`. * **Timelock Management**: * `[Timelocked]` Increase the duration of any timelock (`increaseTimelock`). The timelock for this action equals its own current timelock duration (if set to 0, it can be executed immediately). * `[Timelocked]` Decrease the duration of any timelock (`decreaseTimelock`). The timelock for this action equals the current timelock of the function being decreased. * `[Timelocked]` Irreversibly disable a timelocked action (`abdicate`). ### Allocator [#allocator] The `Allocator` is the active execution role, responsible for deploying liquidity within the boundaries set by the `Curator`. * **Recommended Approach:** A hot-key EOA operated by a bot, or a fast-response multisig. * **Impact of Compromise:** Can misallocate assets between *already approved* adapters, potentially leading to suboptimal yield or temporary liquidity imbalances. Cannot introduce new, unapproved risks. #### Capabilities [#capabilities-2] * Multiple addresses can have this role. * Allocate capital from the vault's idle assets to enabled Adapters (`allocate`). * Deallocate capital from Adapters back to the vault's idle assets (`deallocate`). * Set and manage the `liquidityAdapter` to handle user deposits and withdrawals. * Set the maximum rate at which assets can grow (`setMaxRate`). For permissionless shared-liquidity integration, follow the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). ### Sentinel [#sentinel] The `Sentinel` is a safety-oriented role designed for reactive risk mitigation. It acts as a check on the `Curator` and can intervene in emergencies. * **Recommended Approach:** Can be a hot key, a DAO governance contract (e.g., Snapshot), or a trusted third party. * **Impact of Compromise:** Minimal. A compromised `Sentinel` can only take actions that reduce risk (e.g., deallocate assets) or halt pending changes. It cannot introduce new risk. #### Capabilities [#capabilities-3] * Multiple addresses can have this role. * Deallocate assets from any enabled Adapter back to the vault's idle assets (`deallocate`). * Instantly decrease absolute or relative caps for any risk `id`. * Revoke any pending timelocked action submitted by the `Curator` (`revoke`). For practical UI steps, see [Act as a Sentinel in the Curator App](/curate/tutorials-v2/sentinel/). ### Any Address [#any-address] #### Capabilities [#capabilities-4] * Execute any action submitted by the `Curator` once its timelock has expired. * Trigger a `forceDeallocate` to provide in-kind redemption liquidity (subject to a penalty). ## Morpho Vaults V1 Roles [#morpho-vaults-v1-roles] For reference, here are the roles and capabilities for the Morpho Vaults V1 architecture. It is intended that `Owner`, `Curator`, and `Guardian` should be different addresses. ### Owner [#owner-1] #### Roles [#roles] * **Highly Recommended Approaches:** * Deploy a multisig with minimum 4/6 signers, OR * Use an enterprise MPC wallet with equivalent security controls * Implement strict operational security protocols * Distribute signing authority across trusted entities * Never use single-key EOA (Externally Owned Account) control * **Impact of Compromise:** Complete vault control loss without recovery options #### Capabilities [#capabilities-5] * Only a single address can have this role. * Change `Owner` (2 steps: the new `Owner` has to accept ownership). * Renounce ownership. * Set the `Curator`. * Add/remove addresses with the `Allocator` role. * \[Time-locked] Set the `Guardian`. * Increase the timelock duration for every time-locked function. * \[Time-locked if already set] Decrease the timelock duration for every time-locked function. * Set the performance fee. * Set the fee recipient. * Set the rewards distributor address. * All the capabilities of the `Curator`, the `Allocator` and the `Guardian`. ### Curator [#curator-1] #### Roles [#roles-1] * **Recommended Approaches:** * Deploy a multisig with minimum 2/4 or 3/5 signers, OR * Use an enterprise MPC wallet with multi-party authorization policies * Implement robust operational security protocols * Distribute signing authority with geographical redundancy * **Impact of Compromise:** Elevated risk exposure, recoverable by `Owner` intervention #### Capabilities [#capabilities-6] * Only a single address can have this role. * Decrease a supply cap on a Morpho market. * \[Time-locked] Increase a supply cap on a Morpho market, which includes enabling a new market (by setting a non-zero cap on a not yet enabled market). * \[Time-locked] Submit the forced removal of a market. * Revoke the pending supply cap on a Morpho market. * Revoke the pending removal of a Morpho market. * All the capabilities of the `Allocator`. Note: the `Curator` can't pause the withdrawal of assets. ### Allocator [#allocator-1] #### Roles [#roles-2] * **Recommended Options:** * EOA operated by monitoring bot * Multisig with fast response capabilities (1/3 or 2/4), OR * Enterprise MPC wallet with appropriate authorization controls * **Mitigation Strategy:** Set restrictive market caps to limit potential damage #### Capabilities [#capabilities-7] * Multiple addresses can have this role. * Can modify the allocation between markets and the idle supply in the vault within the bounds set by the `Curator`. * Set the supply queue to some arbitrary queue of markets. * Re-order the withdraw queue by applying a permutation to it. Can omit markets on which the vault has 0 supply and 0 cap to remove it form the withdraw queue. ### Guardian [#guardian] #### Roles [#roles-3] * **Implementation Options:** * Snapshot-based governance system * Aragon DAO integration * Multisig managed by community representatives * **Security Benefit:** Provides emergency intervention capability for users #### Capabilities [#capabilities-8] * Only a single address can have this role. * Can revoke a pending timelock decrease until the previous timelock ends and the new timelock is accepted (by the `Owner`). * Can revoke a pending `Guardian` until the timelock ends and the new `Guardian` is accepted (by the `Owner`). * Can revoke each pending market cap increase until the timelock ends and the new market cap is accepted (by the `Owner` or the `Curator`). * In particular, it cannot revoke a pending fee (submitted by the `Owner`). ### Any address [#any-address-1] #### Capabilities [#capabilities-9] * Can accept the new cap after timelock. * Can accept the new fee after timelock. * Can accept the new guardian after timelock. * Can accept the new Timelock value after the current timelock duration. --- # Security Considerations for Vault Curators Source: https://docs.morpho.org/curate/concepts/security-considerations This document outlines important security considerations and best practices for Morpho Vault curators. It covers vault-specific security topics that are most relevant to curators and advanced users. ## Faulty Oracles [#faulty-oracles] Supplying liquidity in a market with a faulty oracle (one that overestimates the collateral's price) will lead to a loss of assets because health computations and liquidations will not work correctly. ### Donation-Based Inflation Manipulation (Vaults V1 only) [#donation-based-inflation-manipulation-vaults-v1-only] Morpho Vaults V1 (versions 1.0 and 1.1) have a critical additional edge case. When a vault lists a market whose oracle reports a price significantly above the true market price, specifically, when collateral tokens can be acquired below `oraclePrice × LLTV,` the entire vault can be at risk through the following steps: 1. Acquire shares in the vault (by supplying, if the supply queue is not empty). 2. Donate assets to the vault on the broken market (via `supply` on behalf of the vault), inflating the vault's share price. 3. Buy collateral tokens cheaply (below the oracle price) and borrow against them on the broken market to recoup the donation. 4. Finally, withdraw their now-inflated vault position, effectively draining other depositors. An attacker can repeat this in small batches. **Important: Supply caps do not protect against this.** Caps only limit reallocations (curator/allocator actions), not donations (direct `supply` on behalf of the vault). A vault with a faulty oracle market in its withdrawal queue and a cap of zero is fully vulnerable. The potential loss is not bounded by the supply cap. **Loss bound:** The maximum loss equals the total debt that can be taken using collateral acquired below `oraclePrice × LLTV × σ`, where `σ` is the attacker's share of the vault's total supply. If the supply queue is not empty, `σ` can approach 1, and the threshold simplifies to `oraclePrice × LLTV`. ### Vault V2 Exposure [#vault-v2-exposure] Vaults V2 are **not** affected by this edge case when supplying directly to Morpho Variable Rate Markets using the `MorphoMarketV1AdapterV2`. However, if a Vault V2 supplies to a Vault V1 via the `MorphoVaultV1Adapter`, it **is** exposed up to its allocation to that Vault V1. **Recommendation:** Curators using Vault V1 adapters should use Market V1 adapters. ### Prevention [#prevention] 1. **Choose oracles carefully** to minimize the risk of price divergence between the oracle and the true market price. Be especially cautious with NAV-based oracles updated by the token issuer, these can diverge from DEX prices during exploit scenarios. 2. **Remove unused markets from the withdrawal queue**, even if the cap is zero. A market in the withdrawal queue with cap zero is still vulnerable. Only removing it from the withdrawal queue fully eliminates the risk. 3. **Deploy automated monitoring** that detects when oracle prices diverge from market prices. 4. **Build or adopt an automated kill switch** that empties the supply queue when a significant oracle deviation is detected. This is the single most effective automated mitigation. 5. Use Vaults V2 with Market V1 Adapter as described above. ### Response (If a faulty oracle situation occurs) [#response-if-a-faulty-oracle-situation-occurs] **Step 1: Immediate:** **Pause deposits by emptying the supply queue** (removing all markets including idle). This prevents anyone from getting new shares and increasing their `σ`. Curators can automate this: have an allocator that automatically empties the supply queue when any market listed by the vault reports an oracle price that is significantly above the market price. **Do not re-add any markets to the supply queue until the affected market has been fully removed from the withdrawal queue.** Re-opening deposits before removal restores the attack vector. **Step 2:** Remove the affected market from the withdrawal queue as quickly as possible: * If the vault holds zero shares in the market, remove it instantly. * If the market is liquid, withdraw all shares and then remove it instantly. * If the market is illiquid, initiate forced removal and execute it once the timelock expires. Credit to [Madiha](https://x.com/madiha_right) for helping the Morpho team identify and provide these security guidelines to vault curators. ## Adapter Removal Frontrunning [#adapter-removal-frontrunning] When a curator submits a `removeAdapter` proposal, the removal is timelocked. During this window, an allocator can still call `allocate` on the adapter. Since `removeAdapter` does **not** revert when the adapter holds assets, any assets allocated after the removal submission will be silently lost when the removal executes. Before delisting, follow the full [Adapter Soft Deprecation procedure](/curate/tutorials-v2/emergency/#unsafe-adapter-soft-deprecation), which covers both deallocating existing assets and zeroing caps to prevent frontrunning. **Critical: Set Caps to Zero Before Removing an Adapter** Before delisting an adapter, you **must** set both the absolute cap and relative cap to `0` for every ID exclusive to that adapter. This prevents any allocator from frontrunning the timelocked `removeAdapter` call by allocating assets into the adapter during the timelock window. Without this step, an allocator could push assets into the adapter right before removal, and since `removeAdapter` does **not** revert if the adapter still holds assets, those assets would be permanently lost to the vault's depositors. ## Reverting Oracle [#reverting-oracle] When an oracle reverts (e.g., due to underlying feed deprecation by the provider), the market enters a degraded state. Some operations stop working, but users are not permanently stuck. #### What Still Works [#what-still-works] | Function | Works? | Notes | | -------------------- | ------ | -------------------------------------------- | | `supply` | ✅ | No price check needed | | `withdraw` | ✅ | No price check needed | | `repay` | ✅ | No price check needed | | `supplyCollateral` | ✅ | No price check needed | | `borrow` | ❌ | Requires price → reverts | | `withdrawCollateral` | ⚠️ | Only if borrower has exactly 0 borrow shares | | `liquidate` | ❌ | Requires price → reverts | #### How Users Can Exit [#how-users-can-exit] **Lenders:** Withdraw normally, no issue (provided borrowers repayements grant enough available liquidity) **Borrowers:** 1. Repay the full debt (must reach exactly 0 borrow shares) 2. Then withdraw collateral If even 1 borrow share remains, `withdrawCollateral` will revert because it triggers a health check. **Liquidators:** Cannot liquidate, the function requires the oracle. #### What Curators can do [#what-curators-can-do] 1. Set supply cap to 0 to prevent new allocations 2. Contact the feed provider: request they restore the feed temporarily or provide stale prices so users can exit 3. Notify affected borrowers to repay and exit 4. If unrecoverable, use the "Market Reverts" emergency flow for Morpho Vaults V1 in the Curator App (see [Emergency Procedures](/curate/tutorials-v1/emergency/#market-reverts-forced-removal)) #### Prevention [#prevention-1] When selecting oracles and feeds, prefer providers with clear deprecation policies. Ideally, deprecated feeds should return stale prices rather than revert, this allows orderly market wind-down instead of an emergency situation. ## Vault as asset [#vault-as-asset] In this section, we outline security considerations and recommendations for listing 4626 vaults as an asset, collateral, or loan asset. ### Vault as collateral [#vault-as-collateral] #### Liquid & flashloanable 4626 assets with share price decrease [#liquid--flashloanable-4626-assets-with-share-price-decrease] Depositors in ERC4626 vaults with the following properties: * Most of the shares of the vault can be flash loaned * Most of the vault is liquid * The vault's share price can decrease notably and instantly can suffer from inflated loss of funds during a share price decrease. This is not specific to Morpho but a general statement for all ERC4626 vaults. Since assets deposited on Morpho markets can always be flash loaned, below are recommendations to use Morpho Vaults as collateral. #### Morpho Vault V2 [#morpho-vault-v2] **Morpho Vault V2 can be used as collateral**. Vault V2 realizes bad debt through automatic [loss socialization](/curate/tutorials-v2/bad-debt/). Interest and losses are accrued only once per transaction (see [natspec for more details](https://github.com/morpho-org/vault-v2/blob/main/src/VaultV2.sol#L30-L33)). Vault V2 is thus resistant to flashloan shorting attacks but are susceptible to loan shorting attacks. #### Morpho Vault V1.0 [#morpho-vault-v10] **It is not recommended to use Morpho Vault V1.0 as collateral.** Vaults V1.0 (created with [the MetaMorpho Factory V1.0](https://github.com/morpho-org/metamorpho)) have a bad debt realization mechanism and the share price can decrease in the event of bad debt realization in one of the listed markets. These vaults have no mechanism to protect against flashloan shorting attacks and are also subject to standard loan shorting attacks. Since using vaults as collateral increases the proportion of the vault that can be flashloaned, it further amplifies this risk. #### Morpho Vault V1.1 [#morpho-vault-v11] **Morpho Vault V1.1 can be used as collateral.** For Vaults V1.1 (created with [the MetaMorpho Factory V1.1](https://github.com/morpho-org/metamorpho-v1.1)) do not have a bad debt realization mechanism and the share price of the vault cannot decrease, so neither flashloan nor standard loan shorting attacks apply. That said, in the event of bad debt, vault users may be subject to a bank-run scenario where the last depositor to exit bears the full bad debt. Credit to [100proof](https://x.com/1_00_proof) on helping the Morpho team providing those security guidelines to vault curators. ### Vault as loan asset [#vault-as-loan-asset] #### Pricing method [#pricing-method] One of the main considerations when onboarding an asset is what price it should have. A natural way to price vault shares is by using the exchange rate (supported by the [Morpho Oracle V2](https://github.com/morpho-org/morpho-blue-oracles)). We summarise here the risks associated with using this pricing method and recommendations when doing so. #### Manipulations & share price changes [#manipulations--share-price-changes] General manipulations of an ERC4626 are detailed in an [Euler article](https://www.euler.finance/blog/exchange-rate-manipulation-in-erc4626-vaults). Additionally, we should take into account the other possible ways that the share price of a vault can change. We have in the general case: 1. **donations** in general to ERC4626 vaults can cause a sudden price increase. Vaults can have mitigations to this, but most are vulnerable. Morpho Vaults can be affected because of supply on behalf. 2. **rounding errors** (including stealth donations) can change the share price. They are not expected to be significant, including for Morpho Vaults when the vault has a large enough total assets. And the ones specific to each vaults. For example in Morpho vaults: 3. **“economic”**, known scenarios could decrease the share price on Morpho Vaults. They should be accepted by the user for what they are, so they are not considered as blocking. It includes bad debt realization for Morpho Vaults V1.0 (note that public reallocation can be an aggravating factor in this scenario) and forced market removal. The price change by donation causes a price increase, so it naturally is an issue for listing a vault as a loan asset. In particular, it leads to: * the possibility of a future attack like the Cream hack, as soon as the position of those shares is priced * liquidations having an extra incentive that is extremely difficult to mitigate, resulting in borrowers taking unpredictable risks (or not joining at all). **Because of those risks, it is not recommended to list any ERC4626 vault as a loan asset in a Morpho market at the moment.** ## Inflation Front-Running Attack Protection [#inflation-front-running-attack-protection] Morpho Vaults V1, like all ERC4626-compliant vaults, have a potential vulnerability to what is known as an "inflation front-running attack," particularly when the vault is newly created and empty. This vulnerability is explicitly mentioned in the Morpho Vault contract: ```solidity /// @inheritdoc IERC4626 /// @notice For tokens with 18 decimals, the protection against the inflation front-running attack is low. To /// protect against this attack, vault deployers should make an initial deposit of a non-trivial amount in the vault /// or depositors should check that the share price does not exceed a certain limit. ``` ### What is an Inflation Front-Running Attack? [#what-is-an-inflation-front-running-attack] This attack works as follows: 1. An attacker sees a pending transaction where a user intends to deposit into an empty (or nearly empty) vault 2. The attacker front-runs the transaction with a minimal deposit followed by a large donation directly to the vault 3. This artificially inflates the share price of the vault 4. When a user’s deposit execute and is small enough, they receive significantly fewer shares than expected 5. The attacker, as the majority shareholder, can benefit from this manipulation if more than one user deposit receives fewer shares than expected This vulnerability is most pronounced for tokens with 18 or more decimals, where the `DECIMALS_OFFSET` in the Morpho Vault V1 contract is zero. ### Best Practices for Morpho Vault Curators [#best-practices-for-morpho-vault-curators] 1. **Initial Protection**: Always make an initial deposit of at least 1e9 shares (or 1e12 shares for assets with less than 9 decimals) immediately after vault and market creation on behalf of the address `0x000000000000000000000000000000000000dEaD`. Ensure that all of the markets that the vault allocates to have this deposit as well. 2. **Timelock**: Implement a 3-day timelock to give enough time to review any new markets before adding them to the vault's allocation. 3. **Monitoring**: Monitor the vault's share price relative to the underlying asset price to detect potential manipulation ### Technical Details [#technical-details] The vulnerability exists because in ERC4626 vaults, the relationship between shares and assets determines the exchange rate. In an empty vault with no protection mechanisms: 1. The attacker deposits a minimal amount (e.g., 1 wei of the token) and receives 1 share 2. The attacker then transfers a large amount directly to the vault (e.g., 100M tokens) 3. This creates an exchange rate where 1 share ≈ 100M tokens 4. A user depositing 1 token would receive 0 shares due to the inflated exchange rate The protection mechanisms recommended above prevent this by ensuring the vault has enough tokens to dilute to make the share price increase by donation prohibitively expensive or by adding checks that would reject transactions with suspicious exchange rates. ### Additional Resources [#additional-resources] For more information on ERC4626 and the inflation attack vulnerability: * [OpenZeppelin ERC4626 Documentation](https://docs.openzeppelin.com/contracts/5.x/erc4626) * [EIP-4626: Tokenized Vault Standard](https://eips.ethereum.org/EIPS/eip-4626) * [YieldBox Approach to ERC4626 Security](https://github.com/boringcrypto/YieldBox) --- # Timelock Source: https://docs.morpho.org/curate/concepts/timelock The timelock is a cornerstone of security in Morpho Vaults. It is a mandatory waiting period for sensitive administrative actions, designed to protect depositors by ensuring transparency and providing a window to react to proposed changes. This mechanism is fundamental to the noncustodial nature of the vaults. ### The Timelock Process [#the-timelock-process] A timelock enforces a simple but powerful workflow for critical changes: 1. **Submit**: An administrator (the `Curator` in V2 and `Owner` in V1) proposes a change by submitting it to the vault contract. 2. **Wait**: The proposal becomes pending and enters a waiting period. The duration of this period is defined by the timelock. During this time, the change cannot be executed, and anyone can review it onchain. 3. **Execute**: Once the timelock duration has passed, anyone can call the function to execute the proposed change, making it final. This delay is crucial, as it prevents malicious or erroneous changes from taking effect instantly, giving users and safety roles (`Sentinel`/`Guardian`) time to intervene or withdraw their assets. ## Timelocks in Morpho Vaults V2: Granular and Flexible Security [#timelocks-in-morpho-vaults-v2-granular-and-flexible-security] Vaults V2 upgrade the timelock concept significantly by introducing **per-function timelocks**. This allows for a more nuanced and sophisticated approach to security. ### How It Works: Timelocks per Selector [#how-it-works-timelocks-per-selector] Instead of a single, global timelock for the entire vault, a V2 `Curator` can assign a different timelock duration to each sensitive action, identified by its function `selector`. This flexibility allows for risk-based security policies. For example, a Curator might configure their vault with: * A **7-day timelock** for high-risk actions like enabling a new, untested adapter. * A **1-day timelock** for routine, lower-risk actions like appointing a new `Allocator`. * A **0-second timelock** for actions they deem completely safe (though this is less common for production vaults). ### Timelock Management in V2 [#timelock-management-in-v2] The `Curator` has a specific set of tools to manage these timelocks, each with its own security logic: * **Increasing Timelocks (`increaseTimelock`)**: A Curator can increase the duration of any timelock. This action is itself **timelocked**, meaning it must go through the standard submit-and-execute process. However, if the current timelock for `increaseTimelock` is set to 0, it can be executed immediately. * **Decreasing Timelocks (`decreaseTimelock`)**: This action is highly sensitive. The timelock for `decreaseTimelock` **equals the current timelock of the function being decreased**. This prevents a malicious actor from instantly reducing all safety delays to zero, as they must wait through the same delay period as the function they're trying to reduce. * **Abdicating Actions (`abdicate`)**: A Curator can choose to **permanently and irreversibly** disable their ability to submit a certain type of change by setting its timelock to infinity. This is a powerful "trust-hardening" feature. For example, a vault could abdicate the ability to ever set a `Gate`, guaranteeing to its users that it will remain permissionless forever. ## Timelocks in Morpho Vaults V1 [#timelocks-in-morpho-vaults-v1] In contrast, Morpho Vaults V1 use a **global timelock** system. A single timelock duration applies to all protected actions within the vault, such as increasing a market's supply cap or setting the `Guardian`. ### Key Characteristics of V1 Timelocks [#key-characteristics-of-v1-timelocks] * **Flexible Initial Setup**: The timelock can be set to zero at deployment. This is a major convenience that allows vault administrators to perform the initial configuration and market setup instantly without waiting periods. * **Post-Setup Security**: After the initial setup, any change to the timelock must set its duration to be between **1 day and 2 weeks**. * **Asymmetric Updates**: * **Increasing** the timelock takes effect immediately. * **Decreasing** the timelock is itself a timelocked action, requiring a wait equal to the *current* (longer) timelock duration. This V1 model provides robust security while allowing for practical initial vault deployment. ### Viewing Timelock Settings [#viewing-timelock-settings] You can check the current timelock value(s) for any Morpho Vault by visiting the "Read Contract" section on a blockchain explorer and querying the `timelock` function. * In **V2**, this function takes a function `selector` as an argument. * In **V1**, it returns the single, global timelock duration in seconds. ## Comparison: Vaults V2 vs. Vaults V1 [#comparison-vaults-v2-vs-vaults-v1] | Feature | Morpho Vaults V2 | Morpho Vaults V1 (MetaMorpho) | | ------------------------ | ------------------------------------------------------- | ----------------------------------------------------- | | **Timelock Structure** | **Per-function timelocks** (granular control) | **Global timelock** (single duration for all actions) | | **Timelock Assignment** | Different durations per function selector | One duration applies to all protected actions | | **Increasing Timelocks** | **Timelocked** (subject to its own timelock duration) | **Instant** (takes effect immediately) | | **Decreasing Timelocks** | **Equals the timelock of the function being decreased** | Subject to **current timelock** duration | | **Initial Setup** | **Can be set to zero** at deployment (default is 0) | **Can be set to zero** at deployment | | **Permitted Range** | Flexible (can be set to infinity via abdication) | **1 day to 2 weeks** (after initial setup) | | **Abdication Feature** | **Yes** (can permanently disable actions) | **No** | --- # Yield Generation Source: https://docs.morpho.org/curate/concepts/yield Understanding how yield is generated, accounted for, and distributed is fundamental to curating a successful Morpho Vault. The mechanism for handling yield has evolved from a fully automated onchain system in Vaults V1 to an even more streamlined, adapter-based model in Vaults V2.
Vault Architecture
## Yield in Morpho Vaults V1 [#yield-in-morpho-vaults-v1] In Vaults V1, yield generation is a direct and automatic process tied exclusively to **variable rate markets**. ### The V1 Yield Flow [#the-v1-yield-flow] 1. **Allocation**: The vault allocates capital to one or more variable rate market instances, as directed by the `Allocator`. 2. **Interest Paid by Borrowers**: Borrowers in those markets pay interest on their loans according to the market's specific Interest Rate Model (IRM). 3. **Onchain Accrual**: This interest automatically accrues to all lenders in the market, including the Morpho Vault. 4. **Automatic `totalAssets` Update**: When any user interacts with the vault (e.g., deposits or withdraws), the vault contract automatically calculates its total assets by summing up its underlying positions on variable rate markets. This process is fully onchain and reflects the real, up-to-the-second value of its holdings. 5. **Share Price Growth**: As `totalAssets` increases from the accrued interest, the value of each vault share appreciates, delivering yield to depositors. The key takeaway for Morpho Vault V1 is that interest accrual is **automatically tracked onchain**. The Curator's job is to select the right markets, not to manage the yield calculation itself. ## Yield in Morpho Vaults V2 [#yield-in-morpho-vaults-v2] Vaults V2 are designed to be future-proof, capable of allocating assets to countless different yield sources. Like V1, the yield calculation remains fully automated through a streamlined adapter-based architecture. ### Automatic Asset Reporting via Adapters [#automatic-asset-reporting-via-adapters] Each **Adapter** in Vaults V2 implements a `realAssets()` function that reports the current value of all investments it manages. This allows the vault to automatically aggregate yield from diverse sources. * **Adapter Responsibility**: Each adapter is responsible for accurately reporting the current value of its investments through `realAssets()`. * **Automatic Aggregation**: The vault calculates its total assets by summing the `realAssets()` from all adapters plus any idle assets. * **Protocol Flexibility**: This model allows the vault to account for yield from any source while maintaining automated, onchain accuracy. ### The V2 Yield Flow [#the-v2-yield-flow] 1. **Allocation**: The `Allocator` deploys capital to various protocols through **Adapters** (e.g., to variable rate markets, Morpho Vault V1, etc.). 2. **Yield Generation**: Each underlying position generates yield according to its own protocol's rules. 3. **Real-time Reporting**: Each adapter continuously tracks the current value of its investments and reports this via `realAssets()`. 4. **Vault Accrual**: When a user interacts with the vault, the vault automatically queries all adapters' `realAssets()` and updates its `totalAssets` accordingly. 5. **Share Price Growth**: As the real asset values increase from yield generation, the vault's share price increases automatically, delivering yield to depositors. ### Adapter Examples [#adapter-examples] The initial adapter implementations demonstrate this automatic approach: * **`MorphoMarketV1AdapterV2`**: Automatically queries variable rate markets to report the current value of supply positions, including any accrued interest. * **`MorphoVaultV1Adapter`**: Automatically queries the share price of the underlying Morpho Vault V1 to report current asset values. ### Interest Rate Controls [#interest-rate-controls] While yield tracking is automatic, Curators can set additional controls: #### maxRate [#maxrate] A curator-controlled parameter that caps how quickly `totalAssets` can grow, useful for implementing fixed rate distributions or preventing unrealistic yield spikes. The `maxRate` is capped at 200% APR (see `MAX_MAX_RATE` constant [here](https://github.com/morpho-org/vault-v2/blob/00881eb5f5c8031252c7fcdb7cd7e026bb21cfa4/src/libraries/ConstantsLib.sol#L9)) If `maxRate` is 0, yield does not accrue to vault's suppliers. ### Practical Implications for Curators [#practical-implications-for-curators] * **Morpho Vault V1 Curation**: Focus on **market selection** and **liquidity allocation**. Yield accounting is handled by the protocol. * **V2 Curation**: Focus on **protocol selection**, **adapter enablement**, and **capital allocation**. Yield accounting remains automated through adapter reporting, with optional rate controls via `maxRate`. * **Simplified Management**: Unlike manual rate-setting systems, the adapter-based approach eliminates the risk of mispricing yield while maintaining the flexibility to integrate with any protocol. --- # Create a Market Source: https://docs.morpho.org/curate/tutorials-market-v1/creating-market This tutorial guides you through the process of creating and configuring a market in your Morpho vault. A market can be created either through the [Curator App](https://curator.morpho.org/) or directly through the Morpho contract via a block explorer such as Etherscan. You have to carefully validate the `oracle` configuration you expect to use for the market you intend to deploy. Refer to the [previous section](/curate/tutorials-market-v1/deploying-oracle/) to check how to validate an oracle configuration. Make sure to implement protection against inflation front-running attacks. These attacks can allow malicious users to extract value from the market. For detailed explanation and protection measures, refer to the [inflation front-running attack protection section](/learn/resources/risks/#inflation-front-running-attack-protection). For permanent protection, it consists in **supplying** at least 1e9 shares in the market on behalf of the address `0x000000000000000000000000000000000000dEaD` , via the `supply()` function. ## Deploying a market via the Curator App [#deploying-a-market-via-the-curator-app] This is the recommended option, especially for new curators: the [Curator App](https://curator.morpho.org/) guides you through the market parameters in a single form, so it is faster and less error-prone than encoding a contract call by hand. Connect the wallet you want to use, open [the market creation section](https://curator.morpho.org/markets/create), select the chain, and fill in the market parameters: `loanToken`, `collateralToken`, `oracle`, and `lltv`. The `irm` defaults to the Adaptive Curve IRM. The app displays the resulting market `id` once the full creation flow is complete. This includes market deployment, mandatory dead-deposit seeding, and any selected initial position. The market creation flow also includes: * the mandatory [dead deposit](/curate/tutorials-market-v1/dead-deposit/) * an optional initial position (supply, deposit collateral and borrow) to set [a target utilization rate](/curate/tutorials-market-v1/setting-rate/) Learn more about [Morpho Blue markets and their structure](/learn/concepts/blue/). ## Deploying a market via Etherscan [#deploying-a-market-via-etherscan] ### Jump on the Morpho contract [#jump-on-the-morpho-contract] See the [address section](/get-started/resources/addresses/). E.g: Ethereum mainnet address is [0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb](https://etherscan.io/address/0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb#writeContract) ### Fill all attributes [#fill-all-attributes] Jump on the `createMarket` function * `loanToken`: paste the address of the loan token, * `collateralToken`: paste the address of the collateral token, * `oracle`: paste the oracle address, * `irm`: paste the Interest Rate Model address, * `lltv`: the LLTV is defined with 18 decimals. 1e18 represents an LLTV of 100% (which is not enabled) and 945000000000000000 thus represents 94.5%. Below is a short list of things to have in mind when creating a new market: The LLTV a market creator is expecting to use has to be enabled. At first, the following LLTVs have been governance-approved: | LLTV (%) | Solidity Values (scaled by 1e18) | | -------- | -------------------------------- | | 0 | 0 | | 38.5 | 385000000000000000 | | 62.5 | 625000000000000000 | | 77.0 | 770000000000000000 | | 86.0 | 860000000000000000 | | 91.5 | 915000000000000000 | | 94.5 | 945000000000000000 | | 96.5 | 965000000000000000 | | 98.0 | 980000000000000000 | If you consider using a vault as loan or collateral token, please read the section below about [Vault as Asset](/curate/concepts/security-considerations/) * **Issue**: At deployment, a market starts with 0% utilization, which causes interest rates to quickly decay to extremely low levels, creating a poor user experience. * **Solution**: Add $1 of supply and borrow $0.90 immediately after market creation to establish 90% utilization (matching the Adaptive Curve IRM target). This prevents rate decay and ensures market stability from the start. * **Best practice**: Batch the market creation with supply and borrow transactions to achieve your desired utilization target. While 90% is recommended for the default IRM, other targets may be appropriate depending on your specific rate goals. * **Warning**: Without this intervention, markets intended for future use may already have extremely low rates by the time users start interacting with them, requiring significant time to return to reasonable levels. Market Creation Etherscan guide ### Sign the transaction [#sign-the-transaction] You are done. ## Idle markets [#idle-markets] Idle markets on Morpho are markets where assets can not be borrowed. This liquidity earns no yield from borrowers but can be used as an immediately available buffer for withdrawals. Idle markets are used by curators in [liquidity curation](/curate/concepts/liquidity#curation-in-morpho-vaults-v1) for Morpho Vaults V1. To create an idle market, one has to reproduce the same steps than for an any market, except that the only variable field is the `loanToken` one, all other fields are using the 0 address or 0. Here is an example from the USDC idle market on Ethereum (0x54efdee08e272e929034a8f26f7ca34b1ebe364b275391169b28c6d7db24dbc8): * `loanToken`: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 * `collateralToken`: 0x0000000000000000000000000000000000000000 * `oracle`: 0x0000000000000000000000000000000000000000 * `irm`: 0x0000000000000000000000000000000000000000 * `lltv`: 0 Before creating an idle market for a specific asset on a specific chain, make sure the market does not exist already (see [Market ID Generator below](/curate/tutorials-market-v1/creating-market/#resource-market-id-generator)). ## Important Note [#important-note] ### Market Id Retrieval [#market-id-retrieval] When creating a market, a `CreateMarket` event is emitted with the market `id`:
CreateMarket event showing the market ID
To retrieve the market parameters from this `id`: ### Navigate to the Morpho contract on Etherscan [#navigate-to-the-morpho-contract-on-etherscan] ### Go to the "Read Contract" tab [#go-to-the-read-contract-tab] ### Find the `idToMarketParams` function [#find-the-idtomarketparams-function] ### Paste the `id` into the input field and execute [#paste-the-id-into-the-input-field-and-execute] Example: Using the `id`: `0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc`
idToMarketParams function call example
## Resource: Market ID Generator [#resource-market-id-generator] Use this tool to generate the unique market ID from your market parameters: --- # Market Dead deposit Source: https://docs.morpho.org/curate/tutorials-market-v1/dead-deposit This tutorial explains how to set up a dead deposit for a variable rate market. A dead deposit is a security measure against ERC-4626 inflation attacks ([read more](/developers/earn/concepts/vault-mechanics#inflation-attack-protection)). Setting up a dead deposit involves **supplying** **1e9 shares** to the target market, on behalf of address `0x000000000000000000000000000000000000dEaD`, through a simple process on a block explorer like Etherscan, via the `supply()` function. Unlike Vault V2 dead deposits, market dead deposits always require **exactly 1e9 shares** regardless of the asset's decimals. This is because Morpho markets use a fixed `VIRTUAL_SHARES = 1e6` constant (from `ConstantsLib`), not a decimal-dependent formula. For a cbBTC/WBTC market (8 decimals), 1e9 shares costs only 1000 satoshis ($0.63). 1. Find the Morpho contract on the target chain in the [Addresses page](/get-started/resources/addresses#morpho-v1-contracts). 2. Approve the Morpho contract to spend the market's loan token on your behalf. You can do this on the loan token's contract. Approve enough assets to cover 1e9 supply shares.
3. Use the Morpho contract's `supply` function to supply 1e9 shares to the market on behalf of address `0x000000000000000000000000000000000000dEaD`.
Once the transaction is confirmed, the market will be secured with a dead deposit. --- # Deploy an Oracle for Morpho Markets Source: https://docs.morpho.org/curate/tutorials-market-v1/deploying-oracle Oracle deployment is a critical step when creating lending markets in Morpho, as these smart contracts provide essential price information that powers core functionalities like collateral evaluation and liquidation triggers. This guide will walk you through the entire process of configuring, testing, and deploying an oracle using the Morpho ecosystem tools. ## Understanding Oracles in Morpho [#understanding-oracles-in-morpho] Before jumping into deployment, let's review what oracles do in the Morpho ecosystem: * **Purpose**: Oracles provide price data that determines what one token is worth relative to another * **Implementation**: Morpho takes an oracle-agnostic approach, allowing market creators to choose appropriate price feed mechanisms * **Interface**: All oracles implement the `IOracle` interface with a single standardized function: `price()` * **Immutability**: Once a market is deployed, its oracle address cannot be modified, making correct setup crucial ## The Oracle Deployment Process [#the-oracle-deployment-process] This guide covers the complete process of deploying a new oracle using: 1. **MorphoChainlinkOracleV2Factory**: The contract used to create new oracles 2. **Oracle Tester Tool**: A web interface to validate your configuration and prepare deployment 3. **Oracle Decoder**: A tool to run additional checks on your configuration post-deployment ## Step 1: Understanding the Oracle Parameters [#step-1-understanding-the-oracle-parameters] The `MorphoChainlinkOracleV2Factory` requires several parameters that determine how the oracle will calculate prices. Let's break them down: ### Key Parameters [#key-parameters] | Parameter | Description | | ---------------------------- | --------------------------------------------------------------------------- | | `baseVault` | ERC4626 vault for the collateral token (zero address if not applicable) | | `baseVaultConversionSample` | Amount of base vault shares for conversion calculation (use 1 if no vault) | | `baseFeed1` | First price feed for the collateral token (zero address if not needed) | | `baseFeed2` | Second price feed for the collateral token (zero address if not needed) | | `baseTokenDecimals` | Decimal precision of the base/collateral token | | `quoteVault` | ERC4626 vault for the loan token (zero address if not applicable) | | `quoteVaultConversionSample` | Amount of quote vault shares for conversion calculation (use 1 if no vault) | | `quoteFeed1` | First price feed for the loan token (zero address if not needed) | | `quoteFeed2` | Second price feed for the loan token (zero address if not needed) | | `quoteTokenDecimals` | Decimal precision of the quote/loan token | | `salt` | Unique identifier for deterministic address creation (typically "0x") | ### Common Oracle Configurations [#common-oracle-configurations] Different asset pairs require different oracle configurations. Here are some common patterns: 1. **Standard tokens (ETH/USDC)**: Direct price feed with no vaults 2. **Wrapped tokens (wstETH/ETH)**: Requires special exchange rate adapter 3. **Yield-bearing tokens (sDAI/USDC)**: Requires vault configuration (sDAI -> DAI -> USD -> USDC or sDAI -> DAI -> USDC) ## Step 2: Using the Oracle Tester Tool [#step-2-using-the-oracle-tester-tool] The Oracle Tester tool helps you validate your configuration before deployment, saving gas and preventing errors. Here's how to use it:
### Accessing the Oracle Tester [#accessing-the-oracle-tester] ### Navigate [#navigate] Jump on the Morpho Oracle Interface at [oracles.morpho.dev](https://oracles.morpho.dev/oracle-tester) ### Tester section [#tester-section] Verify you are on the "Tester" tab in the navigation menu ### Form section [#form-section] You'll see a form for configuring and testing your oracle ### Basic Steps [#basic-steps] ### Select Network [#select-network] Choose the blockchain where you'll deploy your oracle. All blockchains where Morpho contracts were deployed are supported. Verify the addresses [here](/get-started/resources/addresses/#morpho-contracts). ### Select Assets [#select-assets] * Choose the **Collateral Asset** (the token that will be used as collateral) * Choose the **Loan Asset** (the token that will be borrowed) * If your token isn't listed, click "Add New Asset" to add custom token information ### Advanced Configuration [#advanced-configuration] After selecting your assets, you'll need to fill in the detailed oracle parameters: #### Base (Collateral) Configuration [#base-collateral-configuration] * **Base Vault**: If your collateral is an ERC4626 token, enter the vault address. Otherwise, use the default zero address (`0x0000000000000000000000000000000000000000`). * **Base Vault Conv. Sample**: Default is 1 if using zero address for Base Vault. For ERC4626 tokens, use a value like `1000000000000000000` (1e18) to ensure precision. This value is used to query the `convertToAsset` function on the Base Vault address you put thus needs enough precision. ##### Understanding Vault Conversion Samples [#understanding-vault-conversion-samples] For ERC4626 tokens like sDAI, the Vault Conversion Sample is critical for accurate pricing: 1. **What it does**: The conversion sample represents the amount of vault shares (e.g., sDAI) that will be passed to the vault's `convertToAssets` function to determine the equivalent amount of underlying tokens (e.g., DAI). 2. **Why it matters**: The `convertToAssets` function returns how much of the underlying asset (e.g., DAI) you would receive if you redeemed a given amount of vault shares (e.g., sDAI). 3. **Choosing a value**: * For non-vault tokens: Use `1` * For ERC4626 tokens: Use a value with sufficient precision, typically `1000000000000000000` (1e18) 4. **Example with sDAI**: * When we set `baseVault` to the sDAI vault address (0x83F20F44975D03b1b09e64809B757c47f942BEeA) * And `baseVaultConversionSample` to 1e18 (1 full share) * The oracle internally calls `convertToAssets(1e18)` on the sDAI vault * This returns the current amount of DAI that 1e18 sDAI shares are worth * This exchange rate is then used in the price calculation alongside the DAI/USD price feed 5. **Formula visualization**: For a sDAI/USDC oracle, the pricing path is: ``` sDAI → DAI (via vault conversion) → USD (via DAI/USD feed) → USDC (via USDC/USD feed) ``` The vault conversion is the first step in establishing the price routing path for ERC4626 tokens, allowing accurate relative valuation between yield-bearing tokens and their non-yield-bearing counterparts. * **Base Feed 1**: The primary price feed for your collateral asset. This could be a Chainlink, Redstone, Pyth, or Chronicle, ... * **Base Feed 2**: If needed, a secondary price feed for route calculation. Often the zero address. * **Base Token Decimals**: The decimal precision of your collateral token. In most of the case, it should match the actual collateral token decimals. If a Base Vault is put, use the underlying asset decimals. (Eg: if `sDAI` is put in Base Vault, use `DAI` decimals) #### Quote (Loan) Configuration [#quote-loan-configuration] * **Quote Vault**: If your loan token is an ERC4626 token, enter the vault address. Otherwise, use the zero address. * **Quote Vault Conv. Sample**: Similar to Base Vault Conversion Sample, default is 1. * **Quote Feed 1**: Primary price feed for the loan asset. * **Quote Feed 2**: Secondary price feed if needed. * **Quote Token Decimals**: The decimal precision of your loan token. #### Salt Configuration [#salt-configuration] * **Salt**: This is typically set to `0x` for initial deployments. It's used to create deterministic addresses. This means that changing the salt, with the very same config, will create a different oracle address. The `Base` and `Quote` configuration are not 'strictly' only for pricing the collateral or respectively only for the loan. One needs to understand that base feeds can be read in the 'left to right' direction, where quote from 'right to left' direction in the virtual mental route. Let's take two examples: **Example A:** * Base Feed 1: DAI -> USD * Quote Feed 1: USDC -> USD The virtual route is: * DAI -> USD + USD -> USDC * -> thus DAI -> USDC. An oracle having the previous base and quote feed provided will be usable for a DAI as collateral and USDC as loan assets market. **Example B:** * Base Vault 1: sDAI (implicitly, this corresponds to creating the sDAI -> DAI onchain feed) * Base Feed 1: DAI -> USD * Quote Feed 1: USDC -> USD The virtual route is: * sDAI -> DAI + DAI -> USD + USD -> USDC * -> thus sDAI -> USDC. An oracle having the previous base vault and base and quote feeds provided will be usable for a sDAI as collateral and USDC as loan assets market. #### Scale Factor [#scale-factor] After deployment, the oracle deployed will contain a `SCALE_FACTOR` that is calculated to properly normalize price values between different tokens, taking into account: * Decimal differences between tokens * Feed precision values * Vault conversion samples (for ERC4626 yield-bearing tokens) ##### Scale Factor Computation [#scale-factor-computation] The `SCALE_FACTOR` in the Morpho Chainlink Oracle V2 is calculated to properly normalize price values between different tokens, taking into account: * Decimal differences between tokens * Feed precision values * Vault conversion samples (for ERC4626 yield-bearing tokens) $$ \text{SCALE\_FACTOR} = 10^{\text{exponent}} \times \frac{\text{quote\_vault\_conversion\_sample}}{\text{base\_vault\_conversion\_sample}} $$ $$ \text{exponent} = 36 + \text{quoteContribution} - \text{baseContribution} $$ Where: $$ \text{quoteContribution} = \text{quote\_token\_decimals} + \text{quote\_feed1\_decimals} + \text{quote\_feed2\_decimals} $$ $$ \text{baseContribution} = \text{base\_token\_decimals} + \text{base\_feed1\_decimals} + \text{base\_feed2\_decimals} $$ With the following variables: * **base\_token\_decimals** = Number of decimals in the base token * **quote\_token\_decimals** = Number of decimals in the quote token * **base\_feed1\_decimals** = Decimals in the first base price feed * **base\_feed2\_decimals** = Decimals in the second base price feed * **quote\_feed1\_decimals** = Decimals in the first quote price feed * **quote\_feed2\_decimals** = Decimals in the second quote price feed * **base\_vault\_conversion\_sample** = Base vault conversion sample size * **quote\_vault\_conversion\_sample** = Quote vault conversion sample size ##### Why Scale Factor Could Be Negative or Zero [#why-scale-factor-could-be-negative-or-zero] The scale factor computation can result in undesired values due to a combination of factors: 1. **Exponent Underflow**: If the sum of negative components significantly exceeds the positive components, the exponent could become negative. In Solidity, this would cause the power calculation to result in 0, as integer powers less than 0 return 0. 2. **Division by Large Vault Sample**: When using ERC4626 vaults, if vault conversion sample is too large compared to the power calculation result, integer division can round down to 0. This is what often happens with yield-bearing tokens like sDAI that require large conversion sample values (1e18). 3. **Precision Loss**: If the calculated value is between 0 and 1 (after division by vault conversion sample), Solidity's integer arithmetic will round it down to 0. Finding the right balance for vault conversion samples is critical. The samples need to be large enough to provide sufficient precision when converting share values to asset values, but not so large that they cause the scale factor calculation to underflow or become 0 through division. ##### What to do if Scale Factor is Negative or Zero [#what-to-do-if-scale-factor-is-negative-or-zero] A **feed wrapper** can correct scale factor misalignment by exposing the same underlying price data but altering the feed’s reported decimals. This ensures the scale factor computed in the oracle stays positive and within the expected precision range without changing vault samples or core oracle logic. | **Situation** | **Feed decimals adjustment** | **Where to adjust** | **Effect** | | ----------------------------------- | ---------------------------- | ----------------------- | ----------------------------------------------------------- | | Scale factor = 0 or extremely small | Decrease feed decimals | Base side feed wrapper | Raises scale factor above 0 and restores valid price output | | Scale factor excessively large | Increase feed decimals | Quote side feed wrapper | Brings scale factor back to a stable, precise range | A **Dummy feed** providing an hardcoded 1:1 price but with a specific decimal setup can also be used to adjust final Scale factor result. [Here](https://etherscan.io/address/0xc3866d726C204C0836E0677A31973c649888973D#code) is a **DummyFeed** created by the community to adjust [this oracle](https://etherscan.io/address/0x95E85FeF34B79F0030f41fb619733BF7e024BD87#code). Any developer implementing a feed wrapper or a dummy feed is fully responsible for its correctness and security.\ Wrappers and dummy feeds can modify core oracle behavior and **must be independently audited** before use in production or integration with Morpho contracts. ### Running Tests [#running-tests] After configuring all parameters, click the "Check" button to run a series of tests: 1. **Decimals Check**: Verifies that the token decimals match what's expected 2. **ERC4626 Check**: Confirms if the loan asset is an ERC4626 token 3. **Deployment Check**: Checks if an oracle with these parameters already exists \[*Only on Ethereum and Base*] 4. ~~**Feeds Check**: Feel free to ignore this one~~ \[*Deprecated*] 5. **Price Check**: Compares the calculated oracle price with expected market prices from a USD comparison price Each check will show a status indicator: * ✅ **Green** = Passed * ⚠️ **Yellow** = Warning or needs attention * ❌ **Red** = Failed check ### Generating Deployment Payload [#generating-deployment-payload] If all checks pass, click the "Generate Payload" button to create the transaction data you'll need for deployment. You can: 1. View the payload details directly in the interface 2. Download the payload as a JSON file 3. Open a Safe transaction to deploy using the "Open Safe" button (for multisig deployments) ## Step 3: Deploying the Oracle [#step-3-deploying-the-oracle] Once you've validated your configuration, you have two options for deployment: ### Option A: Deploying via Etherscan [#option-a-deploying-via-etherscan] ### Find the Factory Contract [#find-the-factory-contract] * Navigate to the MorphoChainlinkOracleV2Factory address on Etherscan: * Ethereum: [0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766](https://etherscan.io/address/0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766#writeContract) * Base: Check the [Morpho documentation](/get-started/resources/addresses/) for the latest address ### Connect Your Wallet [#connect-your-wallet] * Click "Connect to Web3" on Etherscan * Connect your wallet (MetaMask, WalletConnect, etc.) ### Fill in the Parameters [#fill-in-the-parameters] * Navigate to the `createMorphoChainlinkOracleV2` function * Input all the parameters you validated with the Oracle Tester * Double-check all values against your test results ### Write Contract [#write-contract] * Click "Write" to send the transaction * Confirm the transaction in your wallet * Wait for the transaction to be confirmed ### Verify Deployment [#verify-deployment] * Check for the `CreateMorphoChainlinkOracleV2` event in the transaction logs * Note the deployed oracle address for future reference ### Option B: Deploying via Safe (Multisig) [#option-b-deploying-via-safe-multisig] ### Prepare the Safe Transaction [#prepare-the-safe-transaction] * Click "Open Safe" in the Oracle Tester after generating the payload * This will open the Safe interface with the transaction data pre-filled ### Review and Submit\*\*: [#review-and-submit] * Review all parameters in the Safe interface * Submit the transaction for signing by your multisig participants * Execute the transaction once you have the required signatures ## Step 4: Final Validation [#step-4-final-validation] After deployment, it's crucial to validate that your oracle is working correctly: ### Use the Oracle Decoder Tool [#use-the-oracle-decoder-tool] * Navigate to [oracles.morpho.dev/oracle-decoder](https://oracles.morpho.dev/oracle-decoder) * Enter your oracle address * Verify that it returns the expected price for your asset pair ### Check the Oracle Price [#check-the-oracle-price] * Call the `price()` function on your deployed oracle * Verify that the returned price is within expected ranges ## Example Configurations [#example-configurations] Here are detailed examples for common token pairs: ### A. cbBTC/USDC Oracle [#a-cbbtcusdc-oracle] ```python baseVault: "0x0000000000000000000000000000000000000000", // No Base Vault baseVaultConversionSample: 1, // Default to 1 as no Base Vault baseFeed1: "0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c", // BTC/USD Feed baseFeed2: "0x0000000000000000000000000000000000000000", // - Default to Zero Address baseTokenDecimals: 8, // BTC Decimals quoteVault: "0x0000000000000000000000000000000000000000", // No Quote Vault quoteVaultConversionSample: 1, // Default to 1 as no Quote Vault quoteFeed1: "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6", // USDC/USD Feed quoteFeed2: "0x0000000000000000000000000000000000000000", // - quoteTokenDecimals: 6, // USDC Decimals salt: "0x0000000000000000000000000000000000000000000000000000000000000000", // one can put any bytes 32 different than a deployed oracle with the very same configuration ```
**Explanation**: * This configuration uses a direct BTC/USD [price feed](https://docs.chain.link/data-feeds/price-feeds/addresses?page=1\&search=0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c) for calculating the cbBTC/USD feed price. * It also uses a USDC/USD [quote feed](https://docs.chain.link/data-feeds/price-feeds/addresses?page=1\&search=0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6) for calculating the USDC/USD feed price. As USDC/USD is used in the quote section, the final route is: cbBTC -> USD -> USDC. ### B. DAI/USDC Oracle - USDC Hardcoded [#b-daiusdc-oracle---usdc-hardcoded] The particularity in this example is that removing the `QuoteFeed1` as it was in previous example acts as if someone was hardcoding the USDC price to 1 in the oracle of the market, because the virtual path is: * **Before**: DAI -> USD -> USDC * **Now Hardcoded**: DAI -> USD (no USD -> USDC price) ```python baseVault: "0x0000000000000000000000000000000000000000", // - baseVaultConversionSample: 1, // - default baseFeed1: "0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9", // DAI/USD Feed baseFeed2: "0x0000000000000000000000000000000000000000", // - baseTokenDecimals: 18, // DAI Decimals (NOT sDAI decimals) quoteVault: "0x0000000000000000000000000000000000000000", // - quoteVaultConversionSample: 1, // - quoteFeed1: "0x0000000000000000000000000000000000000000", // EMPTY Feed quoteFeed2: "0x0000000000000000000000000000000000000000", // - quoteTokenDecimals: 6, // USDC Decimals salt: "0x0000000000000000000000000000000000000000000000000000000000000000", ```
**Explanation**: This configuration is using the chainlink price feed for the DAI -> USD part, and is not pricing USDC -> USD on purpose to showcase the USDC `hardcoding` method. ### C. sDAI/USDC Oracle [#c-sdaiusdc-oracle] ```python baseVault: "0x83F20F44975D03b1b09e64809B757c47f942BEeA", // sDAI Vault baseVaultConversionSample: 1000000000000000000, // 1e18 baseFeed1: "0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9", // DAI/USD Feed baseFeed2: "0x0000000000000000000000000000000000000000", // - baseTokenDecimals: 18, // DAI Decimals (NOT sDAI decimals) quoteVault: "0x0000000000000000000000000000000000000000", // - quoteVaultConversionSample: 1, // - quoteFeed1: "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6", // USDC/USD Feed quoteFeed2: "0x0000000000000000000000000000000000000000", // - quoteTokenDecimals: 6, // USDC Decimals salt: "0x0000000000000000000000000000000000000000000000000000000000000000", ```
**Explanation**: This configuration handles a yield-bearing token (sDAI) by specifying its vault address and using a higher conversion sample to account for the share-to-asset conversion. The price feeds are standard Chainlink feeds for the underlying assets. ### D. sDAI/USDC Oracle - USDC Hardcoded [#d-sdaiusdc-oracle---usdc-hardcoded] ```python baseVault: "0x83F20F44975D03b1b09e64809B757c47f942BEeA", // sDAI Vault baseVaultConversionSample: 10000000000, // 1e10 - Explanation below baseFeed1: "0xAed0c38402a5d19df6E4c03F4E2DceD6e29c1ee9", // DAI/USD Feed baseFeed2: "0x0000000000000000000000000000000000000000", // - baseTokenDecimals: 18, // DAI Decimals (NOT sDAI decimals) quoteVault: "0x0000000000000000000000000000000000000000", // - quoteVaultConversionSample: 1, // - quoteFeed1: "0x0000000000000000000000000000000000000000", // USDC/USD Feed quoteFeed2: "0x0000000000000000000000000000000000000000", // - quoteTokenDecimals: 6, // USDC Decimals salt: "0x0000000000000000000000000000000000000000000000000000000000000000", ```
* **Explanation**: This configuration handles a yield-bearing token (sDAI) by specifying its vault address and using a high conversion sample (1e10) to account for the share-to-asset conversion. The price feed used in Base Feed 1 is a standard Chainlink feed. Why `baseVaultConversionSample` is 1e10 this time? Sometimes a vault conversion sample that is too high will result in a scale factor of 0 (or even cause a revert at deployment). A solution is to find a tradeoff by reducing the vault conversion sample value (base or quote) while keeping it high enough to maintain precision. ### E. wstETH/ETH Oracle [#e-wstetheth-oracle] ``` baseVault: "0x0000000000000000000000000000000000000000", baseVaultConversionSample: 1, baseFeed1: "0x905b7dAbCD3Ce6B792D874e303D336424Cdb1421", // wstETH/stETH adapter baseFeed2: "0x86392dC19c0b719886221c78AB11eb8Cf5c52812", // stETH/ETH price feed baseTokenDecimals: 18, quoteVault: "0x0000000000000000000000000000000000000000", quoteVaultConversionSample: 1, quoteFeed1: "0x0000000000000000000000000000000000000000", quoteFeed2: "0x0000000000000000000000000000000000000000", quoteTokenDecimals: 18, salt: "0x", ``` **Explanation**: This configuration uses multiple feeds to establish a price path from wstETH to ETH. It requires a special adapter contract (`WstEthStEthExchangeRateChainlinkAdapter`) that handles the wstETH to stETH conversion, followed by a standard feed for stETH to ETH. ## Troubleshooting Common Issues [#troubleshooting-common-issues] ### Oracle Deployment Errors [#oracle-deployment-errors] | Issue | Solution | | ---------------------------------------------- | ----------------------------------------------------------------------------- | | "Transaction reverted" | Double-check all addresses and ensure they are valid contract addresses | | "Gas estimation failed" | Your configuration may be invalid or the contract cannot be executed properly | | "Oracle with same parameters already deployed" | Use the existing oracle (check the deployment check results) | ### Price Verification Issues [#price-verification-issues] | Issue | Solution | | ------------------------------------------ | -------------------------------------------------------------------------------------- | | Price significantly deviates from expected | Check that your feeds are correctly configured and that the decimals are accurate | | Price is zero | Ensure that all required feeds are working and that conversion samples are appropriate | | Price overflow/underflow | Adjust conversion samples to ensure mathematical operations don't exceed limits | ## Best Practices for Oracle Deployment [#best-practices-for-oracle-deployment] 1. **Always test before deploying** - Use the Oracle Tester tool to validate your configuration. 2. **Understand token decimals** - Incorrect decimal configuration is a common source of errors. 3. **Use reliable price feeds** - Choose established providers like Chainlink, Redstone, Chronicle, API3, Pyth ... 4. **Check for existing oracles** - Reuse existing oracles when possible to save gas and ensure consistency. 5. **Document your configuration** - Keep a record of all parameters for future reference. 6. **Verify after deployment** - Always check that the deployed oracle returns expected prices. ## Security Considerations [#security-considerations] Since oracles are immutable once deployed and attached to a market, their security is paramount: 1. **Verify the source code** of any adapter contracts you're using 2. **Understand the price path** and how multiple feeds work together 3. **Consider failure modes** in your price feeds and how they could affect the oracle 4. **Test with extreme market conditions** to ensure robustness ## Community Contributions [#community-contributions] The Morpho community has contributed various oracle adapters that can be useful for specific price feed requirements. These community-developed adapters can be found in the [Oracle Community Section](/developers/ecosystem/oracles/). Before building your own adapter, check if the community has already developed a solution for your specific token pair or price feed requirement. ## Getting Help [#getting-help] If you encounter issues during the oracle deployment process: 1. Get in touch via the chatbox on **[help.morpho.org](https://help.morpho.org)** 2. For complex setups, consider reaching out to the Morpho Association or to a curator directly By following this guide, you should be able to configure, test, and deploy oracles for your Morpho markets with confidence. Remember that careful setup is essential as oracles play a critical role in market safety and cannot be changed after market deployment. --- # Handling Permissioned Tokens in the Morpho App Source: https://docs.morpho.org/curate/tutorials-market-v1/permissioned-assets Some loan assets and collaterals supported by the Morpho protocol are permissioned tokens. These tokens require specific contract whitelisting to function properly in the Morpho app (frontend & SDKs). Indeed, Morpho’s Bundler3 system uses adapters to handle token operations and the main Morpho app and SDK require the general adapter to simulate and execute transactions involving permissioned tokens. If the general adapter is not whitelisted, the bundler flow breaks and users cannot interact with permissioned tokens through the app. ## Issuer whitelisting requirements [#issuer-whitelisting-requirements] To enable a permissioned token to work in the Morpho app, the token issuer must whitelist at token level the following contracts: * `Morpho` - core lending protocol * `GeneralAdapter1` (`EthereumGeneralAdapter1` for Ethereum mainnet) - required for the frontend and SDK to handle permissioned tokens All contract deployment addresses are available in [the addresses page](/get-started/resources/addresses/). Without the general adapter whitelisted, the Morpho app cannot simulate or execute user transactions with permissioned tokens. ## Permit2 and approval UX [#permit2-and-approval-ux] Permit2 is not required for permissioned tokens to work with Morpho. It is an optional improvement that can make the frontend flow smoother by letting users sign an off-chain approval instead of sending a separate ERC-20 `approve` transaction. If Permit2 is available on the target chain and the issuer wants to support this signature-based approval flow, the issuer should also whitelist the Permit2 contract at token level. Permit2 contract implementations are maintained in the [Uniswap Permit2 repository](https://github.com/Uniswap/permit2). The Morpho SDK handles this as a best-effort path: when signature support is enabled, it can return Permit / Permit2 signature requests where supported, and otherwise falls back to the standard approval flow. Permissioned-token support should therefore depend on `Morpho` and `GeneralAdapter1` being whitelisted, with Permit2 whitelisting treated as optional UX enhancement. When listing a permissioned token, curators must review and confirm with the asset issuer that the contracts whitelisting is properly implemented. --- # Set the Rate at Target Source: https://docs.morpho.org/curate/tutorials-market-v1/setting-rate This tutorial guides you through the process of setting and managing interest rates for your new markets:
Setting Rates
## Understanding Rate Mechanics [#understanding-rate-mechanics] The Interest Rate Model (IRM) follows specific rules that market creators should understand: | Category | Condition | Rate Change | | ------------------------- | ---------------------- | ------------------------- | | **Instantaneous Changes** | 90% → 0% Utilization | Rate ÷4 | | | 0% → 90% Utilization | Rate ×4 | | | 90% → 100% Utilization | Rate ×4 | | | 100% → 90% Utilization | Rate ÷4 | | **Time-Based Changes** | At 0% Utilization | Rate halves every 5 days | | | At 100% Utilization | Rate doubles every 5 days | | | At 90% Utilization | Rate remains constant | | **Market Creation** | Initial State | 4% APR at 90% utilization | | | Without Activity | Starts at 0% utilization | ## Best Practices for Rate Setting [#best-practices-for-rate-setting] ### Initial Market Setup [#initial-market-setup] * The market starts with 0% utilization despite having a target rate of 4% APR at 90% utilization * Without activity, rates will continuously decrease (halving every 5 days) * To prevent this, seed the market immediately after creation ### Targeting Specific Rates [#targeting-specific-rates] * To increase rates: Set utilization above 90% * To decrease rates: Set utilization below 90% * Avoid 100% utilization as rates will double every 5 days * Plan rate adjustments in advance due to the time-based mechanics ### Avoiding Liquidity Traps [#avoiding-liquidity-traps] * Low rates can create liquidity traps where: * Lenders are discouraged from depositing * Borrowers can't access liquidity due to insufficient deposits * Maintain healthy rates from market launch to ensure proper market function The Vault V2 Public Allocator is not a direct rate-setting control. New shared-liquidity integrations should use the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). For more details: * IRM Documentation: [Concept IRM Doc](/learn/concepts/irm/) * Implementation: [Morpho V1 IRM on GitHub](https://github.com/morpho-org/morpho-blue-irm) --- # Managing Bad Debt: How-to Guide Source: https://docs.morpho.org/curate/tutorials-v1/bad-debt ## Overview [#overview] This guide is designed for Morpho Vaults curators and aims to provide a methodical approach to identifying, processing, and resolving bad debt within the Morpho ecosystem. It clarifies the concepts of unrealized and realized bad debt and outlines the technical and operational steps required for effective remediation. ### Bad Debt Overview [#bad-debt-overview] As a reminder, the health of a borrower's position can be divided into three main categories, with LTV being Loan-To-Value, LLTV being the Liquidation Loan-To-Value (one of the market parameters) and LIF the Liquidation Incentive Factor: 1. If LTV \< LLTV, the position is healthy, and the borrower cannot be liquidated. 2. If LLTV \< LTV ≤ 1/LIF, a liquidator can repay part or all of the user's debt and seize part of its collateral. 3. If 1/LIF \< LTV, a liquidator can seize all the collateral by repaying only a share of the debt. There is no incentive for the liquidator or borrower to repay the remaining debt. The latter is commonly referred to as bad debt. Under situation 3, bad debt can be categorized into two types: 1. **Unrealized** when the liquidation has not occurred yet, 2. **Realized** when the liquidation has occurred, and the entire collateral position is seized. ## How Vaults are handling bad debt [#how-vaults-are-handling-bad-debt] In practice, the remaining debt of the liquidated user, at the market level, affects all existing suppliers of the market. Vault V1.0 and V1.1 are part of them, and handle bad debt in different ways. ### Vault V1.0 [#vault-v10] Morpho Vault V1.0 automatically realizes the loss on all existing depositors at the moment of the bad-debt liquidation event. The share price of the vault will instantaneously decrease. Vaults are from a Morpho Market point of view simple users. Their supply shares value post liquidation incurring bad debt in a market they allocated liquidity are worth a bit less than before the liquidation. The entire vault and depositors respectively are impacted proportionally to the proportion of the share the vault has in the market. ### Vault V1.1 [#vault-v11] Morpho Vault V1.1 do not automatically realize the loss on all existing depositors at the moment of the bad-debt liquidation event. The share price will not decrease. The last depositors will face the bad debt unless action is taken. ## How to cover the bad debt [#how-to-cover-the-bad-debt] Anyone (e.g., a curator or a lender) can decide to cover those bad debt events that have been realized, and this is feasible in different ways depending on the version of the vault. One can call the action to put back some liquidity "Asset injection", which involves supplying methodically sufficient loan tokens to cover the loss in order to: 1. for vault V1.0: restore the vault's share price 2. for vault V1.1: allow any lenders to never get stuck if there was some liquidity shortage. ### Vault V1.0 [#vault-v10-1] #### How to compute the loan token asset lost? [#how-to-compute-the-loan-token-asset-lost] The `amountLost` is retrievable easily thanks to the liquidation event emitted. After liquidation with bad debt realization, the entire Morpho Market suffered a reduction on `totalSupplyAsset` up to the `badDebtAssets` (in loan token units), retrievable at the `liquidate` event level. ```solidity emit EventsLib.Liquidate( id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtAssets, badDebtShares ); ``` Then, one needs to compute in this Morpho Market what was the proportion % (called `proportionPercentage`) of the vault position as the vault is probably not the only supplier into the market. $$ amountLost = proportionPercentage \times badDebtAssets $$ #### Which action to execute? [#which-action-to-execute] The following 3 solutions could be performed: 1. For vault V1.0, one could think about depositing the loan token asset lost (called `AMOUNT_LOST`) by the vault straight into the market, by calling the `supply()` function, and deposit on behalf of the vault address: ```solidity // On the Morpho contract function supply( MarketParams, assets=AMOUNT_LOST, shares=0, onBehalf=0x_VAULT_ADDRESS, 0x, ) ``` However, if any new depositor comes between the time the vault suffered from bad debt, and the new loan token provider triggers the supply, this would indeed favor the new user(s) while they should not have been benefiting from this. 2. Deposit on behalf of each user, coming with more gas consumption. 3. Create an airdrop solution favoring the impacted users. Do not deposit on behalf of 0x000...001 on Vault V1.0 where the bad debt has been realized, as those assets will be lost. That solution will only work for Vault V1.1. ### Vault V1.1 [#vault-v11-1] For vault V1.1, and as stated in the contract, as the bad debt is not realized at the vault level, anyone can compute the amount lost by using the value of lostAssets. #### How to compute the loan token asset lost? [#how-to-compute-the-loan-token-asset-lost-1] The added loss is `lostAssets_after` - `lostAssets_before`. These values are taken by retrieving the onchain `lostAssets` value before and after the liquidation event. #### Which action to execute? [#which-action-to-execute-1] It is suggested to supply on behalf of address(1) on the vault. The loan token provider will thus lose the asset, but this will cover the bad debt event and no lenders will ultimately be stuck if they were to withdraw their assets. ```solidity // Call the deposit function on the Vault contract like this: function deposit(assets,0x000..0001) ``` ## Alternative Approach: Third-Party Risk Mitigation Protocols [#alternative-approach-third-party-risk-mitigation-protocols] Another alternative for users is to explore third-party risk mitigation protocols (e.g., Nexus Mutual) that may offer coverage against smart contract risks. This is independent of Morpho's infrastructure and is not endorsed or provided by Morpho. --- # Checklist to Validate Vault V1 Configuration Source: https://docs.morpho.org/curate/tutorials-v1/checklist This tutorial provides two comprehensive checklists for validating your vault configuration and integrating them before going live. ## Vault and Market creation checklist [#vault-and-market-creation-checklist] Below is a Vault and market checklist for you to review after following the previous process. * [ ] Create a new market. * [ ] Verify the market Oracles. * [ ] Set the correct rate at target for the new market. * [ ] Set the dead deposit for the new market ([tutorial](/curate/tutorials-market-v1/dead-deposit/)). * [ ] Create a Morpho Vault V1.1. * [ ] Set the dead deposit for the new vault ([tutorial](/curate/tutorials-v1/dead-deposit/)). * [ ] Set the correct caps for all listed markets. * [ ] Check that all listed markets have a dead deposit. * [ ] Set the correct roles and permissions. * [ ] Configure the correct timelock after vault creation. * [ ] (optional) Submit rewards program and rewards for the new asset. * [ ] List and price your vault on Coingecko. * [ ] List your tokens on Etherscan. * [ ] Price your tokens on [DeFiLlama](https://docs.llama.fi/coin-prices-api). * [ ] Configure your reallocation bot for rate targeting and liquidations. * [ ] Submit a Forum post. Do not configure a Vault V2 Public Allocator on a Vault V1 vault. New or migrating shared-liquidity integrations should follow the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). ## Integration checklist [#integration-checklist] Morpho has multiple integrations will help distribute the vaults and markets you created * [ ] Integrate your Vaults in Superform, IPOR Fusion, lazy [Summer.fi](http://Summer.fi) , Yearn, Beefy * [ ] Integrate your Markets in Defisaver, Contango, Summer.fi --- # Vault Dead deposit Source: https://docs.morpho.org/curate/tutorials-v1/dead-deposit This tutorial explains how to set up a dead deposit for a Vault V1. A dead deposit is a security measure against ERC-4626 inflation attacks ([read more](/developers/earn/concepts/vault-mechanics#inflation-attack-protection)). Setting up a dead deposit involves minting 1e9 shares (or 1e12 shares for assets with less than 9 decimals) from the target vault, on behalf of address `0x000000000000000000000000000000000000dEaD`, through a simple process on a block explorer like Etherscan: 1. Access the vault contract on the explorer using the vault's address. 2. Approve the Morpho Vault V1 contract to spend the vault's deposit token on your behalf. You can do this on the deposit token's contract. Typically a value of $1.00 should suffice.
Approve (Etherscan)
3. Use the vault contract's `mint` function for 1e9 shares (or 1e12 shares for assets with less than 9 decimals), with address `0x000000000000000000000000000000000000dEaD` as the receiver.
Vault Dead Deposit (Etherscan)
Once the transaction is confirmed, the vault will be secured with a dead deposit. --- # Emergency Procedures Source: https://docs.morpho.org/curate/tutorials-v1/emergency This document outlines procedures for handling emergency situations related to Morpho Vaults, from dealing with unsafe markets to responding to a compromised role. These actions should be executed with extreme care, as they can have significant consequences for the vault and its depositors. Please read the [README](https://github.com/morpho-org/metamorpho/blob/main/README.md) section from the Morpho Vaults repository. ## Unsafe Market: Soft Deprecation [#unsafe-market-soft-deprecation] This procedure is used when a market is deemed too risky for continued new allocations, but is still functioning correctly, allowing for a graceful exit. ### The Goal [#the-goal] Safely withdraw all vault assets from the market without incurring losses, and prevent any new capital from being allocated to it. ### Procedure via Direct Contract Calls [#procedure-via-direct-contract-calls] 1. **Revoke Pending Caps**: If there is a pending cap increase for the market, the `Guardian`, `Curator`, or `Owner` should call `revokePendingCap` to prevent it from being accepted. 2. **Set Cap to Zero**: The `Curator` or `Owner` must immediately call `submitCap` with a `newSupplyCap` of `0`. This is an instant action and prevents new assets from flowing into the market. * **Pro Tip**: To avoid front-running issues, it's best to batch the `revokePendingCap` and `submitCap` calls in a single transaction using a `multicall` function for instance. 3. **Reallocate Liquidity**: The `Allocator` should call `reallocate` to withdraw all available liquidity from the market. If the market is illiquid, this may need to be done in stages as liquidity becomes available. 4. **Update Withdraw Queue**: The `Allocator` should move the market to the beginning of the `withdrawQueue` to ensure any newly available liquidity is captured first. 5. **Complete Delisting**: Once all assets have been withdrawn, the `Allocator` can remove the market entirely from the `withdrawQueue` by calling `updateWithdrawQueue`. ### Procedure via the Curator App [#procedure-via-the-curator-app] The Curator App streamlines this process with a dedicated "Unsafe Market" emergency flow.
Executing this flow in a single transaction will: 1. Revoke any pending cap changes for the target market. 2. Set the market's supply cap to `0`. 3. Reallocate all *currently available* liquidity to the vault's designated Idle Market. 4. Move the market to the first position in the `withdrawQueue`. This flow performs a "soft deprecation." If the market is illiquid, the vault will retain its position, and the Allocator must monitor it to withdraw the remaining assets as they become available. ## Market Reverts: Forced Removal [#market-reverts-forced-removal] This procedure is for critical situations where a market is malfunctioning (e.g., its functions consistently revert), making it impossible to withdraw assets normally. **This action involves accepting the loss of any capital stuck in the market.** ### The Goal [#the-goal-1] Remove a broken market from the vault's accounting to restore normal vault operations, even if it means losing the assets allocated to it. ### Procedure via Direct Contract Calls [#procedure-via-direct-contract-calls-1] 1. **Set Cap to Zero**: As with a soft deprecation, the `Curator` must first set the market's cap to `0`. 2. **Submit for Removal**: The `Curator` or `Owner` must call `submitMarketRemoval`. This action is **timelocked**. 3. **Wait for Timelock**: The mandatory waiting period must elapse. 4. **Update Withdraw Queue**: After the timelock, the `Allocator` can call `updateWithdrawQueue` to permanently remove the market from the vault's configuration. **Warning: Potential Loss of Funds** Executing a forced removal will cause the vault to abandon any assets still allocated to the market. This procedure should **only** be used when assets are considered otherwise irrecoverable. ### Procedure via the Curator App [#procedure-via-the-curator-app-1] The "Market Reverts" flow in the Curator App is designed for this scenario.
In a single transaction, this flow will: 1. Revoke any pending cap changes for the market. 2. Set the market's supply cap to `0`. 3. Call `submitMarketRemoval` to begin the timelocked process of forced deprecation. After the timelock expires, the `Allocator` must still manually call `updateWithdrawQueue` to complete the removal. ### Impact on Users [#impact-on-users] Once the market has been forced removed from the withdraw queue, there will be different consequences based on whether the vault is a Vault V1.0 or V1.1: #### Vault V1.0 [#vault-v10] The lost assets will be socialized among all depositors immediately. Some options for this loss to be covered are listed in the [Curate: Manage Bad Debt section](/curate/tutorials-v1/bad-debt#which-action-to-execute) of the docs. Do not deposit on behalf of 0x000...001 on Vault V1.0 where the bad debt has been realized, as those assets will be lost. That solution will only work for Vault V1.1. **Re-adding the market:** If the market starts working again, it can be re-added. The share price will increase back as the assets become accessible again, effectively reversing the loss socialization. #### Vault V1.1 [#vault-v11] The loss of assets is not realized by depositors. However -if the assets are not recovered-, the last depositors to withdraw will suffer the entire loss and won't be able to withdraw their assets. **Do not re-add a force-removed market on Vault V1.1.** Re-adding would cause the share price to increase (as the assets become visible again), but the internal `lostAssets` accounting remains unchanged. This creates bad debt that cannot be resolved. On Vault V1.1, the loss due to a forced market removal can be prevented if the lost assets are covered by making a deposit to the vault for the amount of the lost assets, on behalf of address `0x000...001`: ```solidity vault.deposit(amount, 0x000...001); ``` Make sure to check that the vault is V1.1 before depositing on behalf of `0x0000000000000000000000000000000000000001`, otherwise this deposit will be lost. ## Role Compromise Scenarios [#role-compromise-scenarios] ### Curator Takeover [#curator-takeover] If a `Curator` begins acting maliciously (e.g., submitting caps for unsafe markets), the `Owner` must intervene. 1. **Set a New Curator**: The `Owner` should immediately call `setCurator` to transfer control to a new, trusted address. 2. **Revoke Pending Actions**: The new `Curator`, `Owner`, or `Guardian` must call `revokePendingCap` on any malicious proposals submitted by the old curator. 3. **Remediate Accepted Actions**: If a malicious cap was accepted before it could be revoked, the new `Curator` must follow the "Unsafe Market" procedure to safely exit the position. ### Allocator Takeover [#allocator-takeover] If an `Allocator` acts against the vault's strategy (e.g., by setting improper queues or reallocating assets incoherently), the `Owner` should: 1. **Revoke Allocator Privileges**: Call `setIsAllocator` with the malicious address and `false`. 2. **Correct Queues**: The `Owner` or a trusted `Allocator` must call `setSupplyQueue` and `updateWithdrawQueue` to restore the correct order. 3. **Reallocate Funds**: Call `reallocate` to move assets back into alignment with the vault's intended strategy. --- # Curate Allocations (Vaults V1) Source: https://docs.morpho.org/curate/tutorials-v1/manage-allocations Managing allocations is the core responsibility of the `Allocator` role. It involves strategically moving capital between the vault's enabled markets to optimize for yield, maintain liquidity, and adhere to the risk parameters set by the `Curator`. This guide covers how to manage allocations using two methods: 1. **The Curator App**: An intuitive interface for rebalancing the vault's portfolio. 2. **Direct Contract Calls**: For advanced users and scripted interactions with the powerful `reallocate` function. ## Method 1: Managing Allocations with the Curator App [#method-1-managing-allocations-with-the-curator-app] The Curator App provides a visual and interactive way to rebalance your vault's assets. Navigate to the **Allocation** tab to get started. ### Viewing and Planning Allocations [#viewing-and-planning-allocations] The main table on this page displays the vault's current, live allocation across all its enabled markets, including the Idle Market. You can customize the table with additional columns to see real-time data like supply caps, utilization, and interest rates.
### Performing a Reallocation [#performing-a-reallocation] ### Enter Edit Mode [#enter-edit-mode] Click the **Reallocate** button to make the "New Allocation" column editable. This also reveals several helpful tools at the top right of the table: * **Reset**: Reverts your pending changes back to the vault's live state. * **Simulate**: Analyzes the impact of your proposed reallocation on the vault's overall APY. * **Undo/Redo**: Steps back and forth through your recent changes.
### Define the New Allocation [#define-the-new-allocation] You can now input your desired allocation for each market. Use the toggle at the top of the column to switch between entering values as percentages (%) or as absolute asset amounts. As you edit, the "Delta" column will show you how much capital will be moved for each market.
### Save and Execute [#save-and-execute] Once you are satisfied with your new allocation strategy, click the **Save** button to initiate the transaction. The app will generate the necessary `reallocate` call for you to confirm in your wallet. If your new allocation doesn't account for 100% of the vault's assets, a popup will prompt you to allocate the small remaining amount to a market of your choice (typically the Idle Market) to ensure no assets are left unallocated.
## Method 2: Reallocating via Direct Contract Calls [#method-2-reallocating-via-direct-contract-calls] For advanced control and scripting, you can call the `reallocate` function directly on the vault contract. This function is extremely powerful, as it can simultaneously withdraw from some markets and supply to others in a single, gas-efficient transaction. ### Understanding the `reallocate` Function [#understanding-the-reallocate-function] The function signature is: `reallocate(MarketAllocation[] calldata allocations)` It takes a single argument: an array of `MarketAllocation` structs. Each struct tells the vault how much of the underlying `asset` it should aim to have in a specific market **after** the reallocation is complete. ```solidity struct MarketAllocation { MarketParams marketParams; // The full parameters of the target market uint256 assets; // The desired final amount of assets in this market } ``` ### How `reallocate` Works [#how-reallocate-works] 1. **Calculates Net Flow**: The function first determines the total amount of assets that need to be withdrawn from markets where the target allocation is *less* than the current allocation. 2. **Withdraws First**: It performs all necessary withdrawals from the designated markets. 3. **Supplies Second**: It then uses the pool of withdrawn assets to supply to markets where the target allocation is *greater* than the current allocation. 4. **Balance Check**: The transaction will revert if the total amount supplied does not exactly match the total amount withdrawn. ### Practical Example: Shifting Funds [#practical-example-shifting-funds] Imagine a vault with 100 WETH, currently allocated as: * Market A: 70 WETH * Idle Market: 30 WETH The Allocator wants to move 20 WETH from Market A to a new, higher-yielding Market B. **The `allocations` array would look like this:** ```json [ { "marketParams": { ...Market A params... }, "assets": "50000000000000000000" // Target: 50 WETH }, { "marketParams": { ...Market B params... }, "assets": "20000000000000000000" // Target: 20 WETH } ] ``` **What happens when this is executed?** 1. The vault sees it needs to withdraw `70 - 50 = 20 WETH` from Market A. 2. It withdraws 20 WETH from Market A. 3. It sees it needs to supply `20 - 0 = 20 WETH` to Market B. 4. It supplies the 20 WETH to Market B. 5. The total withdrawn (20) equals the total supplied (20), so the transaction succeeds. The Idle Market was not included, so its balance of 30 WETH remains untouched. ### Best Practice: The "Max" Catcher [#best-practice-the-max-catcher] To ensure that the total supplied always matches the total withdrawn and to avoid failed transactions due to rounding or accrued interest, it is a **critical best practice** to make the last item in your `allocations` array a "catcher." This is typically your Idle Market, with its `assets` parameter set to the maximum possible `uint256` value (`115792089237316195423570985008687907853269984665640564039457584007913129639935`). **Example: Safely shifting 20 WETH from Market A to Market B** ```json [ { "marketParams": { ...Market A params... }, "assets": "50000000000000000000" // Target: 50 WETH }, { "marketParams": { ...Market B params... }, "assets": "20000000000000000000" // Target: 20 WETH }, { "marketParams": { ...Idle Market params... }, "assets": "115792089237316195423570985008687907853269984665640564039457584007913129639935" // MAX_UINT256 } ] ``` In this case, the Idle Market will automatically absorb any remaining assets from the withdrawn pool, guaranteeing the transaction succeeds. This is especially useful for fully delisting a market by setting its target `assets` to `0`. --- # Curate Markets & Liquidity (Vaults V1) Source: https://docs.morpho.org/curate/tutorials-v1/manage-markets As a curator for a Morpho Vault V1, your primary responsibilities are to manage which markets the vault can supply to, set risk limits via supply caps, and organize the flow of capital using supply and withdraw queues. This guide provides a comprehensive overview of how to perform these actions using two methods: 1. **The Curator App**: A user-friendly interface for most management tasks. 2. **Direct Contract Calls**: For advanced users or scripted interactions via Etherscan. ## Curating Supply Caps [#curating-supply-caps] Supply caps are the primary tool for controlling a vault's risk exposure to any single Morpho Market. ### Using the Curator App [#using-the-curator-app] Navigate to the **Caps** tab in the Curator App to view and manage all active and pending market caps.
#### Modifying or Adding a New Cap [#modifying-or-adding-a-new-cap] ##### Click "+ New" Button [#click--new-button] Click the "+ New" button in the top right to open the market selection modal.
##### Select a Market and Set the Cap [#select-a-market-and-set-the-cap] Choose the market you want to enable or modify. Set the desired **Supply Cap** value and click `Submit New Cap`. Setting a non-zero cap for the first time will enable the market in your vault.
##### Wait for the Timelock and Accept [#wait-for-the-timelock-and-accept] Increasing a cap is a timelocked action. After submitting, the proposal will appear in the **Pending** tab. Once the timelock expires (shown under "Estimated Valid At"), you must click **Accept** to finalize the change. Decreasing a cap is instant.
### Using Direct Contract Calls [#using-direct-contract-calls] ### 1. Submit the Cap (`submitCap`) [#1-submit-the-cap-submitcap] Execute `submitCap` with the market's parameters and the desired cap amount. The `newSupplyCap` should be in the native decimals of the loan token.
Etherscan interface for submitCap
Submitting a cap via Etherscan.
### 2. Accept the Cap (`acceptCap`) [#2-accept-the-cap-acceptcap] After the timelock period expires, anyone can call `acceptCap` with the same market parameters to finalize the change. **How to get a market's ID?** You can find a market's ID from: * The `CreateMarket` event log at market creation. * The main `app.morpho.org` interface. * Using a script, like [this one](https://gist.github.com/tomrpl/3cfd34e04a01f9cbae2b16887f8026cf).
Etherscan interface for acceptCap
Accepting a cap via Etherscan.
## Managing Queues [#managing-queues] The Supply and Withdraw Queues dictate the logic for capital allocation. The `Allocator` role is responsible for their management. ### Using the Curator App [#using-the-curator-app-1] Navigate to the **Queues** section to view and manage your vault's queues.
#### Reordering a Queue [#reordering-a-queue] 1. Click the **Edit** button for the queue you wish to modify. 2. **Drag and drop** the market cards into your desired order. 3. Click **Save** and confirm the transaction.
### Using Direct Contract Calls [#using-direct-contract-calls-1] #### Setting the Supply Queue (`setSupplyQueue`) [#setting-the-supply-queue-setsupplyqueue] To set the supply queue, call `setSupplyQueue` with an array of market `Id`s in the desired order. ```bash # Example input for setSupplyQueue [0xc54d7acf14de29e0e5527cabd7a576506870346a78a11a6762e2cca66322ec41, 0x_MARKET_ID_2, ...] ``` #### Updating the Withdraw Queue (`updateWithdrawQueue`) [#updating-the-withdraw-queue-updatewithdrawqueue] To reorder the withdraw queue, call `updateWithdrawQueue` with an array of numbers representing the **original indexes** of the markets in their **new desired order**. For example, if the original queue was `[MarketA, MarketB, MarketC]` (indexes `[0, 1, 2]`) and you want the new order to be `[MarketC, MarketA, MarketB]`, you would call `updateWithdrawQueue([2, 0, 1])`. **Idle Market Best Practices** * **Idle Market** should be first in the `supplyQueue` and in the `withdrawQueue`. ## Delisting a Market [#delisting-a-market] Delisting a market is a multi-step process to ensure assets are safely migrated. ### Standard Delisting Process [#standard-delisting-process] This process safely removes a market without losing assets. ### 1. Set Market Cap to Zero [#1-set-market-cap-to-zero] Instantly set the market's supply cap to `0` using `submitCap`. This prevents new allocations to the market. ### 2. Reallocate All Liquidity [#2-reallocate-all-liquidity] Use `reallocate` to move all of the vault's assets out of the target market and into other active markets. Ensure the market to be delisted is **not** included in the `reallocate` call. ### 3. Update the Withdraw Queue [#3-update-the-withdraw-queue] Once the market's cap and the vault's allocation are both zero, call `updateWithdrawQueue` with a new index array that omits the delisted market. Before removing a market from the withdraw queue, ensure: 1. The market cap is `0`. 2. There are no pending cap changes for that market. 3. The vault's allocation in that market is `0`. Failure to meet these conditions will cause the transaction to revert. ### Dust Shares Removal [#dust-shares-removal] There is an edge case to consider when removing a market. A small amount of dust supply shares could accumulate through usual allocation processes. Reallocating out of these markets via reallocate is not possible because the reallocation is denominated in assets, while the removal of a market from the withdraw queue requires supply shares to be zero. When you have some dust shares on a market that you are trying to remove, **you are forced to supply one unit of an asset on behalf of your vault** to then be able to reallocate and withdraw. For example for USDC it would be 0.000001$ (6 decimals). **Warning**: You must not force remove a market if this edge case occurs: * in V1.0 force-removing it makes share price goes down instantly and will go up instantly if you re-enable the market. * in V1.1, force-removing it creates a bad debt in the vault instantly and will not go away if you re-enable it (instead, the share price will go up, but the same hole will be present). #### Remove dust shares via the Curator App V1 (Ethereum mainnet and Base only) [#remove-dust-shares-via-the-curator-app-v1-ethereum-mainnet-and-base-only] ### 1. Head to the Emergency page [#1-head-to-the-emergency-page] Click on the "Dust Shares Removal" `start` button.
### 2. Choose the targeted market [#2-choose-the-targeted-market]
### 3. Follow the app [#3-follow-the-app] The flow will require to: * Approve the Bundler V2 The Curator app V1 uses the Bundler V2 for this flow - never give approval to the Bundler3 manually. * Donate 1 unit of the asset on behalf of the vault * Remove dust by reallocating
#### Remove dust shares using direct contract calls [#remove-dust-shares-using-direct-contract-calls] For other chains than Ethereum and Base, you will have to follow the same steps as described above but interactign at contract directly (via script or explorer). ### 1. Approve the Morpho contract and supply 1 unit of asset in the market on behalf of the vault. [#1-approve-the-morpho-contract-and-supply-1-unit-of-asset-in-the-market-on-behalf-of-the-vault] See the [Morpho contract addresses](/get-started/resources/addresses#morpho-v1-contracts) and the `supply` function spec [here](/get-started/resources/contracts/morpho/#supply). ### 2. You can then `reallocate` liquidity out of the market [#2-you-can-then-reallocate-liquidity-out-of-the-market] See how to interact at contract level [here](/curate/tutorials-v1/manage-allocations/#method-2-reallocating-via-direct-contract-calls) ### Emergency Market Removal [#emergency-market-removal] This process should only be used if a market is malfunctioning and assets cannot be safely reallocated. ### 1. Submit Market for Removal [#1-submit-market-for-removal] As the `Curator`, call `submitMarketRemoval` with the parameters of the market to be force-removed. ### 2. Wait for the Timelock [#2-wait-for-the-timelock] A timelock period begins, allowing users to react. ### 3. Update the Withdraw Queue [#3-update-the-withdraw-queue-1] After the timelock expires, the `Allocator` can call `updateWithdrawQueue` to remove the market, even if it still contains vault assets. **Risk of Fund Loss** Using `submitMarketRemoval` may result in the permanent loss of any assets remaining in the malfunctioning market. This is an emergency action and should be used with extreme caution. ### Delisting Process Summary [#delisting-process-summary] For safe market delisting: 1. Remove the market from the supply queue 2. Reduce the market cap to zero 3. Reallocate all assets from the market to other markets 4. Remove the market from the withdraw queue For emergency situations: 1. Submit market removal 2. Wait for timelock to elapse 3. Update the withdraw queue Always prioritize the safety of depositor assets and provide clear communication throughout the delisting process. --- # Manage Vault Roles Source: https://docs.morpho.org/curate/tutorials-v1/roles This tutorial guides you through the process of setting up and managing roles for your Morpho vault. Proper role assignment is crucial for vault operations, governance, and security. Each role has specific permissions and responsibilities. The Owner has the highest level of control and can assign or revoke other roles. Refer to the [roles concept section](/curate/concepts/roles/) for detailed explanations of each role's capabilities. Be careful when assigning roles, especially the Guardian role, as they have significant control over vault operations. Always verify addresses before executing transactions. ## Using the Curator App [#using-the-curator-app] To manage your vault's roles, navigate using the left-hand side menu to the Roles section.
### Owner Transfer [#owner-transfer] The vault owner can transfer ownership to a new address. Click the Transfer button next to the current owner address. The new owner must accept the transfer to complete the process. Owner transfer is permanent once accepted. Ensure the new owner address is correct and trusted. ### Setting Up Core Roles [#setting-up-core-roles] The following roles can be configured through the interface or directly via contract calls. ## Using Contract Calls [#using-contract-calls] ### Set Curator [#set-curator] The curator manages market configurations and can submit caps and market removals. Execute `setCurator(0xNewCurator)` * **Parameters:** * `newCurator`: Address of the new curator * **Permissions:** Only owner can execute * **Effect:** Immediate - no timelock required ### Add Allocators [#add-allocators] Allocators can reallocate liquidity between markets and manage supply/withdraw queues. Execute `setIsAllocator(0xNewAllocator, true)` * **Parameters:** * `newAllocator`: Address to grant allocator permissions * `isAllocator`: `true` to grant, `false` to revoke * **Permissions:** Only owner can execute * **Effect:** Immediate - can have multiple allocators ### Set Guardian [#set-guardian] The guardian can revoke pending operations and provides additional security oversight. Execute `submitGuardian(0xNewGuardian)` * **Parameters:** * `newGuardian`: Address of the new guardian * **Permissions:** Only owner can execute * **Timelock:** If no guardian exists, sets immediately. Otherwise, requires timelock period * **Acceptance:** New guardian must call `acceptGuardian()` after timelock expires ### Configure Fee Structure [#configure-fee-structure] Performance fees are collected when vault performance exceeds benchmarks. **Step 1: Set Fee Recipient** Execute `setFeeRecipient(0xFeeRecipient)` **Step 2: Set Fee Rate** Execute `setFee(10000000000000000)` // 1% fee (18 decimal precision) * **Parameters:** * `feeRecipient`: Address to receive performance fees * `fee`: Fee rate in 18-decimal format (1% = 10^16) * **Limits:** Maximum fee is 50% (0.5e18) * **Requirements:** Fee recipient must be set before setting non-zero fee ### Set Skim Recipient [#set-skim-recipient] The skim recipient receives rewards and excess tokens allocated to the vault. Execute `setSkimRecipient(0xSkimRecipient)` * **Parameters:** * `skimRecipient`: Address to receive skimmed tokens * **Purpose:** Collects rewards from market participation * **Usage:** Call `skim(tokenAddress)` to transfer accumulated tokens ## Role Management via Curator App [#role-management-via-curator-app]
### Guardian Changes [#guardian-changes] Guardian changes require timelock approval for security: 1. Click **Change** next to the current guardian 2. Enter the new guardian address 3. Submit the transaction - this starts the timelock period 4. Wait for the timelock to expire 5. New guardian must click **Accept** to complete the change ### Curator Management [#curator-management] Curator changes are immediate and don't require timelock: 1. Click **Change** next to the current curator 2. Enter the new curator address 3. Submit the transaction - change takes effect immediately ### Allocator Management [#allocator-management] **Adding Allocators:** * Click **Add Allocator** in the top right * Enter the allocator address in the popup * Submit the transaction **Removing Allocators:** * Click **Remove** on the relevant allocator row * Confirm the transaction Multiple allocators can be active simultaneously. Both the owner and curator can add or remove allocators. This page covers Vault V1 role management. Do not configure a Vault V2 Public Allocator on a Vault V1 vault. For new or migrating shared-liquidity integrations, follow the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). Custom Vault V1 allocator roles remain available where required. ## Best Practices [#best-practices] * **Owner:** Use a secure multisig wallet for the owner role * **Curator:** Choose experienced DeFi professionals familiar with Morpho markets * **Guardian:** Select a trusted party different from the curator for oversight * **Fee Recipient:** Can be the same as owner or a separate treasury address * **Allocators:** Assign only reviewed custom allocator addresses required by the Vault V1 strategy Always double-check addresses before submitting role assignment transactions. Incorrect addresses cannot be easily reverted and may compromise vault security. --- # Unwind a Morpho Vault V1 Source: https://docs.morpho.org/curate/tutorials-v1/unwind-vault-v1 This guide explains how to safely wind down a Morpho Vault V1, ensuring depositors can withdraw their assets while removing market exposure and preventing new capital from entering. Communicate with depositors before starting. Make sure they are aware of the timeline and that yield will drop to zero as assets move to idle. If possible, also warn borrowers in the underlying markets, withdrawing liquidity increases utilization and can drive up borrow rates. ## Step 1: Prevent New Deposits [#step-1-prevent-new-deposits] Set the supply cap to `0` for every active market in the vault. Since this is a cap decrease, it takes effect **instantly** without waiting for a timelock. The preferred way to do this is via the **Curator App**: navigate to the **Caps** tab, set each market cap to `0`, and submit. See the [Curating Supply Caps](/curate/tutorials-v1/manage-markets/#curating-supply-caps) tutorial for a step-by-step walkthrough. Once all market caps are `0`, the vault's supply queue no longer has any market to deposit into, and any incoming deposit will revert. Depositors can still withdraw freely. ## Step 2: Remove Market Exposure [#step-2-remove-market-exposure] The `Allocator` moves assets from each active market to the vault's **idle market** (zero-yield position). The preferred way to do this is via the **Curator App**: navigate to the **Allocation** tab, set all active markets to `0` and direct the full amount to idle, then submit. See the [Managing Allocations](/curate/tutorials-v1/manage-allocations/) tutorial for a detailed walkthrough including the "max catcher" best practice. If a market is **illiquid** (high utilization), this step must be repeated incrementally as liquidity becomes available. Set up monitoring to track when assets can be withdrawn and an automation to automatically reallocate assets to idle. ## Step 3: Reduce Yield to Zero [#step-3-reduce-yield-to-zero] Once all assets are in the idle market, the vault yields **0%**. This is the natural outcome of completing Steps 1–2. No additional action is required to suppress yield. ## Optional: For listed vaults [#optional-for-listed-vaults] If your vault is **listed on the Morpho app**, reach out to the Morpho Association to coordinate the off-chain wind-down: * **Display a deprecation warning** on the vault's page in the app, informing depositors that the vault is being wound down. * **Remove the vault from the main listing** once deposits are blocked and the wind-down is underway. * **Notify users** through the usual communication channels (governance forum, curator socials). --- # Create a Vault Source: https://docs.morpho.org/curate/tutorials-v1/vault-creation This tutorial guides you through the process of creating and configuring a new vault in the Morpho ecosystem. Morpho Vaults can be created directly via etherscan (or any other explorer) or using scripts or using apps. Morpho Vaults curator is supposed to be a knowledgeable DeFi user, as monitoring liquidity on Morpho requires skills, and has some risks. Refer to the [risk section](/learn/resources/risks/). Before operating a vault, make sure to implement protection against inflation front-running attacks. These attacks can allow malicious users to extract value from your vault. For detailed explanation and protection measures, refer to the [inflation front-running attack protection section](/learn/resources/risks/#inflation-front-running-attack-protection). For permanent protection, it consists in depositing at least 1e9 shares (or 1e12 shares for assets with less than 9 decimals) in the vault on behalf of the address `0x000000000000000000000000000000000000dEaD` ## Using the Curator App [#using-the-curator-app] To create a new Vault navigate using the left-hand side menu to the Create Vault section.
By default, the owner of the Vault will be the address connected and submitting the transaction. You can create the Vault with any address and set the ownership to another, overriding the default by simply entering a different address. By default, the Timelock value is set to 1 Day, however Curators can set it to 0 to facilitate the initial setup (ie. adding markets to the vault without waiting for the Timelock.) The Timelock can be increased after the creation of the vault, but it cannot be set to 0 again after creation. The rest of the fields are: * **Asset:** The address of the Vault’s deposit asset - cannot be modified after creation * **Name:** The display name of the Vault, which can be modified after creation * **Symbol:** The ticker of the vault’s receipt token - cannot be modified after creation * **Salt** (optional): A unique value to deterministically generate the Vault’s address (in conjunction with the other parameters) Once the vault is configured with the desired parameters, click on Create Vault to submit the transaction. ## Using Etherscan [#using-etherscan] ### Factory Address [#factory-address] Jump into the factory contract. You can find factory addresses on [the Addresses section here](/get-started/resources/addresses/). Always double check the address you are clicking on. ### Fill attributes [#fill-attributes] One will have to fill the following attributes: * `initialOwner`: The initial owner of the vault.\ This address will have the authority to manage the vault's settings and parameters after creation. * `initialTimelock`: The initial duration, in seconds, during which any critical operations (e.g. changing the curator or guardian roles, updating market parameters) are delayed for security reasons. Starting from V1.1, the initial timelock can be set to 0 during vault creation to facilitate the initial configuration. After setup, the timelock must be set within bounds (minimum = 1 day = 86,400 seconds and maximum = 2 weeks = 1,209,600 seconds).

Timelock value:

It can be convenient to set the timelock to 0 during the initial configuration phase to complete it faster. Once configuration is complete, any owner can increase the timelock value to the required minimum of 86,400 seconds (1 day) or higher.
* `asset`: The address of the underlying asset (ERC20 token) for which the vault is being created. The vault will manage deposits and withdrawals of this asset. * `name`: The human-readable name of the vault.\ It is used primarily for display and identification purposes. * `symbol`: The abbreviation or ticker symbol of the vault.\ This symbol is a shorter way to represent the vault, typically used in interfaces and listings. * `salt`: A unique value used in conjunction with the vault's other parameters to deterministically generate its address through the CREATE2 opcode. This ensures the uniqueness of the vault's address even if other parameters are identical.
Vault Creation
### Approve [#approve] Approve the transaction, and retrieve the address of your vault.
--- # Abdicate Gates Source: https://docs.morpho.org/curate/tutorials-v2/abdicate-gates This guide explains why and how curators can abdicate the **three critical gates** to help ensure users retain access to their assets, preserving the vault's non-custodial properties. ## Why Abdicate Gates? [#why-abdicate-gates] Morpho Vault V2 implements four gates that control access to vault operations. Three of these gates are **critical** because they can potentially block users from accessing their assets: | Gate | Risk | Why It's Critical | | ----------------------- | ------------------------------------------------------- | -------------------------------------------------- | | **Receive Shares Gate** | Can prevent users from receiving shares during deposits | Users could deposit assets but not receive shares | | **Send Shares Gate** | Can lock users out of withdrawals and transfers | Users cannot exit the vault | | **Receive Assets Gate** | Can prevent users from receiving assets on withdrawal | Users can burn shares but not receive their assets | The fourth gate (**Send Assets Gate**) is non-critical because it only restricts who can deposit-it cannot block existing users from withdrawing. ### Non-Custodial Guarantee [#non-custodial-guarantee] By abdicating the three critical gates to `address(0)`, a curator permanently guarantees that: * No access restrictions can ever be imposed on withdrawals * Users can always exit the vault and receive their assets * The vault remains truly non-custodial This is an **irreversible** action that provides depositors with the strongest possible assurance. ## Prerequisites [#prerequisites] Before abdicating, ensure: 1. All three critical gates are set to `address(0)` (no gate configured) 2. You are connected with the **Curator** wallet 3. You understand this action is **permanent and irreversible** ## Tutorial: Abdicate Gates on the Curator App V2 [#tutorial-abdicate-gates-on-the-curator-app-v2] ### Head to the Curator App V2 [#head-to-the-curator-app-v2] [curator.morpho.org](https://curator.morpho.org/vaults) On your Morpho Vault V2 page, head to the **Timelocks** tab and click on the **Abdicate** button.
You can select the gate you want to abdicate and submit the abdication.
Once the abdication is submitted, it will appear in the **Pending Timelocks**, click there and accept the abdication once the timelock has passed.
You can then check the gate function has been abdicated in the **Gates** table of the Timelocks page.
## Tutorial: Abdicate Gates via Etherscan [#tutorial-abdicate-gates-via-etherscan] Abdication follows a **two-step timelocked process**: first submit, wait for the timelock, then execute. ### Step 1: Verify Current Gate Settings [#step-1-verify-current-gate-settings] 1. Go to your vault's contract on Etherscan 2. Navigate to **Read Contract** 3. Check the following functions return `0x0000000000000000000000000000000000000000`: * `receiveSharesGate()` * `sendSharesGate()` * `receiveAssetsGate()` If any gate is not `address(0)`, you must first set it to zero using the corresponding setter function (also timelocked). ### Step 2: Submit Abdication [#step-2-submit-abdication] Navigate to **Write Contract** and connect your Curator wallet. For each gate you want to abdicate, call the `submit` function with the encoded `abdicate` call data: | Gate to Abdicate | Data to Submit | | ------------------- | ---------------------------------------------------------------------------- | | Receive Shares Gate | `0xb2e328482cb19f9800000000000000000000000000000000000000000000000000000000` | | Send Shares Gate | `0xb2e32848c21ad02800000000000000000000000000000000000000000000000000000000` | | Receive Assets Gate | `0xb2e3284804dbf0ce00000000000000000000000000000000000000000000000000000000` | 1. Find the `submit` function 2. Enter the data from the table above 3. Click **Write** and confirm the transaction 4. Repeat for each gate ### Step 3: Wait for Timelock [#step-3-wait-for-timelock] After submitting, you must wait for the vault's timelock period to elapse before executing. To check the timelock duration, call `timelock(bytes4 selector)` with the `abdicate` selector: `0xb2e32848`. ### Step 4: Execute Abdication [#step-4-execute-abdication] Once the timelock has passed, execute each abdication: 1. Find the `abdicate` function 2. Enter the gate setter selector: * Receive Shares Gate: `0x2cb19f98` * Send Shares Gate: `0xc21ad028` * Receive Assets Gate: `0x04dbf0ce` 3. Click **Write** and confirm the transaction ### Function Selector Reference [#function-selector-reference] | Gate Setter Function | Selector | | ------------------------------- | ------------ | | `setReceiveSharesGate(address)` | `0x2cb19f98` | | `setSendSharesGate(address)` | `0xc21ad028` | | `setReceiveAssetsGate(address)` | `0x04dbf0ce` | | `setSendAssetsGate(address)` | `0x871c979c` | ### Step 5: Verify Abdication [#step-5-verify-abdication] After all transactions confirm, verify the abdication was successful: 1. Go to **Read Contract** 2. Call `abdicated(bytes4 selector)` with each gate setter selector 3. Confirm all three return `true` ## Using Solidity [#using-solidity] ### Step 1: Submit Abdications [#step-1-submit-abdications] ```solidity import {IVaultV2} from "@morpho-org/vault-v2/interfaces/IVaultV2.sol"; // Submit abdication for all three critical gates vault.submit(abi.encodeCall(IVaultV2.abdicate, (IVaultV2.setReceiveSharesGate.selector))); vault.submit(abi.encodeCall(IVaultV2.abdicate, (IVaultV2.setSendSharesGate.selector))); vault.submit(abi.encodeCall(IVaultV2.abdicate, (IVaultV2.setReceiveAssetsGate.selector))); ``` ### Step 2: Execute After Timelock [#step-2-execute-after-timelock] ```solidity // After timelock has passed, execute each abdication vault.abdicate(IVaultV2.setReceiveSharesGate.selector); vault.abdicate(IVaultV2.setSendSharesGate.selector); vault.abdicate(IVaultV2.setReceiveAssetsGate.selector); ``` Abdication is **irreversible**. Once abdicated, the gate setter function can never be called again, permanently locking the gate to its current value (`address(0)`). --- # Handling Bad Debt: Morpho Vault V2 Source: https://docs.morpho.org/curate/tutorials-v2/bad-debt ## Overview [#overview] This guide is designed for Morpho Vault V2 curators and explains how bad debt is handled through the vault's loss socialization mechanism. ### Bad Debt Recap [#bad-debt-recap] As a reminder, the health of a borrower's position can be divided into three main categories, with LTV being Loan-To-Value, LLTV being the Liquidation Loan-To-Value (one of the market parameters) and LIF the Liquidation Incentive Factor: 1. If LTV \< LLTV, the position is healthy, and the borrower cannot be liquidated. 2. If LLTV \< LTV ≤ 1/LIF, a liquidator can repay part or all of the user's debt and seize part or all of its collateral. 3. If 1/LIF \< LTV, a liquidator can seize all the collateral by repaying only a share of the debt. There is no incentive for the liquidator or borrower to repay the remaining debt. The latter is commonly referred to as bad debt. Under situation 3, bad debt can be categorized into two types: * **Unrealized** when the liquidation has not occurred yet. * **Realized** when the liquidation has occurred, and the entire collateral position is seized. ## How Morpho Vault V2 Handles Bad Debt [#how-morpho-vault-v2-handles-bad-debt] Morpho Vault V2 implements an automatic loss socialization mechanism. When bad debt occurs in an underlying market or adapter, the loss is detected and reflected in the vault's share price. ### Loss Detection [#loss-detection] Each adapter reports the actual current value of its allocated assets via a `realAssets()` function. When bad debt occurs in an underlying market, the value reported by the adapter automatically decreases. ### Loss Realization [#loss-realization] During interest accrual, the vault compares the reported real asset value to its internally tracked total: * If `realAssets` has dropped below the previously recorded `totalAssets`, the vault updates its accounting downward. * Interest calculation uses `zeroFloorSub()`, which floors at zero, meaning no negative interest is generated. * The vault's `totalAssets` is updated to `min(realAssets, maxTotalAssets)`. ### Automatic Socialization [#automatic-socialization] Losses are socialized proportionally because share value is determined via convertToAssets(), which computes shares × (totalAssets + 1) / (totalSupply + virtualShares). When totalAssets decreases, so does the value returned by this function. When `totalAssets` decreases: * Each share is worth less. * No shares are burned - all shareholders absorb the loss equally. * Depositors who entered before the loss event bear the loss proportionally to their share of the vault. ## Important Considerations for Curators [#important-considerations-for-curators] ### Management Fees During Loss Periods [#management-fees-during-loss-periods] Management fees continue to accrue even during periods where losses are realized. Curators should be aware that fees are computed before loss realization in the interest accrual flow. ### Morpho Vault V1.1 Adapter Caveat [#morpho-vault-v11-adapter-caveat] Morpho Vaults V1.1 do not realize bad debt internally. If a Morpho Vault V2 supplies to a Morpho Vault V1.1 via a `MorphoVaultV1Adapter`, the corresponding losses from that allocation will **not** be reflected in the Morpho Vault V2's share price. Curators should account for this when allocating to Morpho Vaults V1.1. ### Flash Loan Protection [#flash-loan-protection] The vault includes safeguards against share price manipulation around loss realization events. Losses are only accounted for once per transaction, preventing attackers from shorting shares around a known loss event. --- # Checklist to Validate Vault V2 Configuration Source: https://docs.morpho.org/curate/tutorials-v2/checklist This checklist covers the setup of a Morpho Vault V2 using a single `MorphoMarketV1AdapterV2` to curate one or several variable rate markets. ## Vault V2 Setup Checklist [#vault-v2-setup-checklist] * [ ] **Create a new Morpho Vault V2** using the `VaultV2Factory`. * [ ] **Set the core roles**: As `Owner`, assign the `Curator` and `Sentinel(s)`. * [ ] **Set the Morpho Adapter Registry**: As `Curator`, submit and execute the timelocked proposal to set the `adapterRegistry` to the official Morpho registry. * [ ] **Abdicate `setAdapterRegistry`**: As `Curator`, renounce the ability to change the adapter registry to lock in the official registry permanently. * [ ] **Abdicate Gates**: As `Curator`, if needed, abdicate the three critical gates to remove the ability to restrict deposits and withdrawals ([More on Gate Abdication here](/curate/tutorials-v2/abdicate-gates/)). * [ ] **Deploy the `MorphoMarketV1AdapterV2`**. * [ ] **Enable the adapter**: As `Curator`, submit and execute the timelocked proposals to link the adapter to your vault. * [ ] **Appoint the `Allocator(s)`**: As `Curator`, grant the `Allocator` role to your allocator contract or multisig via a timelocked proposal. * [ ] **Set the risk caps**: As `Curator`, submit and execute the timelocked proposals to set `absoluteCap` and `relativeCap` on the vault for each variable rate market you want to include. * [ ] **Set the `liquidityAdapter`**: As `Allocator`, designate the underlying variable rate market (within the `MorphoMarketV1AdapterV2`) as the default market for handling deposits and withdrawals (usually, a highly liquid market). * [ ] **Set the dead deposit** for the vault itself ([tutorial](/curate/tutorials-v2/dead-deposit/)). * [ ] **Set the dead deposit** for each listed market ([tutorial](/curate/tutorials-market-v1/dead-deposit/)). * [ ] **Harden the vault's timelocks**: After the initial setup is complete, increase the timelocks for sensitive functions to a production-safe value. * [ ] **Configure your reallocation, monitoring and liquidation bots**: Ensure your `Allocator` logic is in place to allocate the vault's assets. ## Integration Checklist [#integration-checklist] Morpho has multiple integrations that will help distribute the vaults you create. * [ ] Integrate your Morpho Vault V2 in Superform, IPOR Fusion, Summer.fi, Yearn, Beefy. * [ ] Integrate your underlying Markets in Defisaver, Contango, Summer.fi. * [ ] List and price your new vault token on platforms like Coingecko and Etherscan. --- # Morpho Vault V2 Dead Deposit Source: https://docs.morpho.org/curate/tutorials-v2/dead-deposit A dead deposit is a security measure against ERC-4626 inflation attacks ([read more](/developers/earn/concepts/vault-mechanics#inflation-attack-protection)). On an **empty** vault, execute: ```solidity vault.mint(TARGET_SHARES, 0x000000000000000000000000000000000000dEaD); ``` The dead deposit must be the **very first transaction** on an empty vault. If any user deposit occurs first, the inflation attack may already have been executed. ## How Many Shares to Mint [#how-many-shares-to-mint] The number of shares to mint depends on the vault's underlying asset decimals. Use the table below for common tokens: | Token | Decimals | Shares to Mint | Assets You'll Spend | USD Cost | | ------ | -------- | ------------------------- | ------------------- | ----------------------------- | | WETH | 18 | 1,000,000,000 | \~0 ETH | \< $0.1 | | wstETH | 18 | 1,000,000,000 | \~0 wstETH | \< $0.1 | | sUSDS | 18 | 1,000,000,000 | \~0 sUSDS | \< $0.1 | | sUSDe | 18 | 1,000,000,000 | \~0 sUSDe | \< $0.1 | | siUSD | 18 | 1,000,000,000 | \~0 siUSD | \< $0.1 | | cbBTC | 8 | 10,000,000,000,000,000 | 0.01 BTC | \~$630 (at current BTC price) | | WBTC | 8 | 10,000,000,000,000,000 | 0.01 BTC | \~$630 (at current BTC price) | | USDC | 6 | 1,000,000,000,000,000,000 | 1 USDC | \~$1 | | PYUSD | 6 | 1,000,000,000,000,000,000 | 1 PYUSD | \~$1 | | USDT | 6 | 1,000,000,000,000,000,000 | 1 USDT | \~$1 | **New or unlisted asset?** Use the formula: ``` targetShares = max(1e9, 10^(6 + max(0, 18 - decimals))) ``` ## How to Execute via Explorer [#how-to-execute-via-explorer] ### 1. Verify the vault is empty [#1-verify-the-vault-is-empty] Navigate to the Morpho Vault V2 contract on explorer → **Read Contract** → check that `totalSupply()` returns `0`. ### 2. Approve the vault to spend your tokens [#2-approve-the-vault-to-spend-your-tokens] Go to the underlying token's explorer page → **Write Contract** → call `approve(vaultAddress, assetsAmount)`. Use the approval amounts below: | Decimals | Approval amount | | -------- | --------------- | | 18 | 1000000000 | | 8 | 1000000 | | 6 | 1000000 |
### 3. Mint shares to `0xdead` [#3-mint-shares-to-0xdead] Go to the vault contract on Etherscan → **Write Contract** → call `mint(TARGET_SHARES, 0x000000000000000000000000000000000000dEaD)`. Use the **Shares to Mint** value from the table above for your token.
### 4. Verify the dead deposit [#4-verify-the-dead-deposit] On the vault contract → **Read Contract** → call `convertToAssets(1000000000000000000)`. The result should be approximately `10^decimals` (i.e. 1.0 token per share).
## Additional Requirements [#additional-requirements] Beyond the vault-level dead deposit, the following must also be satisfied: * `0xdead` must have **1e9 supplyShares** in each variable rate market with a non-zero cap on the vault. * `0xdead` must have **1e9 shares** in each Morpho Vault V1 with a non-zero cap on the vault, as well as in each market in their withdraw queue. ## Why These Numbers [#why-these-numbers] The required shares satisfy two constraints simultaneously: 1. **Inflation attack prevention**: at least 1e6 assets must be in the vault, limiting any single manipulation to a relative change of at most 1/1,000,000. This requires `shares >= 1e6 × virtualShares`. 2. **Share price rounding protection**: prevents the share price from crashing to near-zero when an adapter rounds tiny balances to 0. This requires `shares / (shares + virtualShares) >= 0.999`. The inflation attack constraint dominates (by a factor of 1,000), so the formula `max(1e9, 1e6 × virtualShares)` satisfies both. The 1e9 floor applies the existing per-market/per-vault minimum for 18-decimal assets where `virtualShares = 1`. `virtualShares = 10^max(0, 18 - decimals)` is a constant defined in the VaultV2 constructor. --- # Emergency Procedures (Vaults V2) Source: https://docs.morpho.org/curate/tutorials-v2/emergency This document outlines procedures for handling emergency situations in Morpho Vaults V2. These actions should be executed with extreme care, as they can have significant consequences for the vault and its depositors. Before proceeding, ensure you are familiar with [Roles & Capabilities](/curate/concepts/roles/), [Timelocks](/curate/concepts/timelock/), and [Adapters](/curate/tutorials-v2/listing-adapters/). You can perform most emergency actions directly in the [Curator app](https://curator.morpho.org/vaults). Select your vault and open the **Emergency Actions** tab. For `maxRate` changes, use the **Max Rate** section on the main vault overview page. The UI prepares the same onchain transactions; the role requirements and timelock rules below still apply. If you are acting as a Sentinel, see [Act as a Sentinel in the Curator App](/curate/tutorials-v2/sentinel/) for the dedicated Sentinel workflow. ## Quick Reference [#quick-reference] **Key V2 design principle:** Emergency functions that *reduce* risk (revoking, decreasing caps, deallocating) are never timelocked. Only functions that could *increase* risk are timelocked. | Action | Timelocked? | Who Can Execute | | ---------------------------------------------- | ---------------------- | ------------------------------------------- | | Revoke any pending timelocked action | No | Curator, Sentinel | | Decrease absolute/relative caps to 0 | No | Curator, Sentinel | | Deallocate assets to idle | No | Allocator, Sentinel | | Set `maxRate` to `0` | No | Allocator | | Force deallocate (in-kind redemption) | No | Anyone (permissionless, subject to penalty) | | Remove allocator role (`setIsAllocator`) | Yes (configurable) | Curator via `submit` | | Remove adapter (`removeAdapter`) | Yes | Curator via `submit` | | Burn shares (adapter-level, MarketV1AdapterV2) | Yes (adapter timelock) | Curator via adapter `submit` | *** ## Unsafe Market: Soft Deprecation [#unsafe-market-soft-deprecation] Use this procedure when a specific market (or all markets sharing a collateral type) is deemed too risky, but the adapter itself is functioning correctly. The adapter remains active - you are only restricting and exiting the specific market. This is the **most common emergency action** and should be the default response when a risk concern is identified at the market level rather than the adapter or protocol level. ### Curator App procedure [#curator-app-procedure] This can be implemented via normal flows on the Curator App. 1. On the **Timelocks** tab, under Vault Pending Actions, identify any pending cap increases for the affected market or collateral asset. Select and revoke them. The Sentinel can also use the Sentinel tab to revoke pending actions (see how [here](/curate/tutorials-v2/sentinel/#revoke-a-pending-action)).
2. Under **Caps** tab, find the affected market or collateral asset. Set the absolute and relative caps to `0` and confirm.
3. Under **Allocation** tab, find the affected market. Enter the amount to withdraw and click **Deallocate** to move assets back to idle. If the market is illiquid, repeat as liquidity becomes available.
### Script procedure [#script-procedure] **Step 1 - Revoke Pending Cap Increases for the Affected Id(s)** If there are pending cap increases for the market-level or collateral-level id, the `Sentinel` or `Curator` should call `revoke(bytes)` to cancel them. Both absolute and relative cap increases should be revoked if pending. ```solidity // Revoke a pending absolute cap increase on a specific market id bytes memory pendingAbsoluteData = abi.encodeCall(IVaultV2.increaseAbsoluteCap, (marketIdData, newCap)); vault.revoke(pendingAbsoluteData); // Revoke a pending relative cap increase on a specific market id bytes memory pendingRelativeData = abi.encodeCall(IVaultV2.increaseRelativeCap, (marketIdData, newRelativeCap)); vault.revoke(pendingRelativeData); ``` **Step 2 - Decrease the Market or Collateral Cap to Zero** The `Curator` or `Sentinel` calls `decreaseAbsoluteCap` and/or `decreaseRelativeCap` for the **specific id** associated with the affected market or collateral. This is an **instant** action - it is explicitly exempt from timelocks. To deprecate a single market: ```solidity // idData targets the specific market // e.g. keccak256(abi.encode("this/marketParams", adapterAddress, marketParams)) vault.decreaseAbsoluteCap(marketIdData, 0); vault.decreaseRelativeCap(marketIdData, 0); ``` To deprecate all markets sharing a collateral type: ```solidity // idData targets the collateral // e.g. keccak256(abi.encode("collateralToken", collateralTokenAddress)) vault.decreaseAbsoluteCap(collateralIdData, 0); vault.decreaseRelativeCap(collateralIdData, 0); ``` The adapter-level cap and other unaffected market caps remain unchanged, allowing the adapter to continue operating normally for its other markets. **Step 3 - Reallocate Liquidity Out of the Affected Market** The `Allocator` or `Sentinel` calls `deallocate` to withdraw assets from the affected market. These assets can be: * Moved as **idle assets** in the vault. * **Reallocated into other markets** within the same adapter (via a subsequent `allocate` call), if those markets are healthy and have available cap room. * Deposited into the **Liquidity Adapter**, if it has available cap room. ```solidity // Deallocate from the affected market into vault idle assets vault.deallocate(adapter, affectedMarketData, assets); // Optionally reallocate into a healthy market on the same adapter vault.allocate(adapter, healthyMarketData, assets); ``` The `assets` parameter requires an exact amount - neither the vault nor the `MorphoMarketV1AdapterV2` adapter interprets `type(uint256).max` as "withdraw all." You must query the current allocation and pass the precise value. If the market is illiquid, this may need to be done in stages as liquidity becomes available. In this case, monitoring should be set up to deallocate progressively. *** ## Phantom Interest Accrual: Freeze the Share Price [#phantom-interest-accrual-freeze-the-share-price] Use this procedure when an allocated market keeps reporting accrued interest that will not be recoverable. This happens when the collateral is illiquid or insolvent, the borrower position is heading toward bad debt, and liquidations are broken (liquidate function is reverting) or aren’t economical (no liquidators are liquidating unhealthy positions). In this case, the adapter's `realAssets()` will continue to climb from interest accrual on the underlying market, lifting the vault share price even though that interest will never be recovered by the vault, resulting in phantom interest. This is dangerous even after you have zeroed caps and started deallocating. The inflated share price creates a first-mover advantage: lenders who withdraw while idle liquidity lasts redeem at the inflated price, extracting interest that was never backed by recoverable assets. They leave with their principal plus the phantom interest, draining the vault's liquidity and leaving the remaining lenders unable to withdraw and exposed to a greater share of the eventual loss. Setting `maxRate` to `0` caps future `totalAssets` growth, freezing the share price against further unrealizable interest. It does not unwind phantom interest that already accrued before the change, so freeze before moving more liquidity to idle whenever possible; any liquidity that is already idle while the share price is inflated can still be withdrawn by early exiters. ### How `maxRate = 0` works [#how-maxrate--0-works] The vault bounds interest accrual by `maxRate` in `accrueInterestView()`: ```text maxTotalAssets = totalAssets + totalAssets * elapsed * maxRate / WAD newTotalAssets = min(realAssets, maxTotalAssets) ``` * With `maxRate = 0`, `maxTotalAssets = totalAssets`, so `newTotalAssets = min(realAssets, totalAssets)`. Any value `realAssets()` reports above the current `totalAssets` is ignored: upward accrual is frozen. * The cap is one-directional. If `realAssets()` falls below `totalAssets` (a realized or socialized loss), `newTotalAssets = realAssets` and the loss is still booked down to all depositors. Freezing `maxRate` stops phantom gains from being distributed; it does not prevent real losses from being socialized. * `setMaxRate` calls `accrueInterest()` before updating the value, so interest already accrued under the previous cap is locked in. Only future growth is stopped, so set it as early as possible. `maxRate` is a single vault-level parameter, not per-market. Setting it to `0` halts yield accrual for the **entire vault**, including any healthy positions it still holds. This is the intended trade-off during remediation. Restore a non-zero `maxRate` once the unsafe exposure has been fully removed. `maxRate = 0` neither realizes nor hides bad debt. If liquidations socialize a loss, the share price still moves down. The action stops further unrealizable interest from being added to the share price; it does not remove any already-accrued inflation, so execute it before deallocating additional liquidity to idle when possible. ### Curator App procedure [#curator-app-procedure-1] 1. Follow the [Unsafe Market: Soft Deprecation Curator App procedure](/curate/tutorials-v2/emergency/#curator-app-procedure) for the affected market, but only complete step 1 and step 2 first: **revoke pending cap increases**, then **set the absolute and relative caps to `0`**. Do not complete the deallocation step yet. 2. Stay on the main vault overview page. In the **Max Rate** section, set **Max Rate** to `0` and confirm the transaction.
3. After the `maxRate = 0` transaction is confirmed, return to [step 3 of the Unsafe Market: Soft Deprecation Curator App procedure](/curate/tutorials-v2/emergency/#curator-app-procedure): use the **Allocation** tab to deallocate available liquidity to idle. If the market is illiquid, repeat as liquidity becomes available. 4. Once the unsafe exposure has been removed and the share price again reflects only recoverable assets, return to the **Max Rate** section on the main vault overview page and restore an appropriate non-zero value. `setMaxRate` is restricted to the `Allocator` role. The Curator app prepares the transaction, but the signing wallet must have the `Allocator` role. ### Script procedure [#script-procedure-1] **Step 1 - Revoke pending cap increases and set caps to zero** Follow the first two parts of [Unsafe Market: Soft Deprecation](/curate/tutorials-v2/emergency/#script-procedure): in that section's Script procedure, use Step 1 to revoke any pending cap increases, then Step 2 to set the absolute and relative caps to `0` for the affected id(s). This stops new allocation to the unsafe market. Do **not** deallocate available liquidity to idle before freezing accrual unless there is an overriding operational reason. Moving liquidity to idle while the share price is still inflated can increase the amount early withdrawers can redeem at the inflated price. **Step 2 - Freeze accrual with `maxRate = 0`** An `Allocator` calls `setMaxRate` on the vault. This is **instant** and is **not timelocked**. ```solidity // Freeze totalAssets growth: no further interest is distributed to suppliers vault.setMaxRate(0); ``` `setMaxRate` is restricted to the `Allocator` role. The `Sentinel` **cannot** set it. If no trusted allocator is available, the `Curator` must first assign one via `setIsAllocator`, which is subject to its own timelock. **Step 3 - Deallocate liquidity after the freeze** After `maxRate` is set to `0`, follow Step 3 - Reallocate Liquidity Out of the Affected Market in the [Unsafe Market: Soft Deprecation Script procedure](/curate/tutorials-v2/emergency/#script-procedure). The soft-deprecation substep does not have its own anchor; look for the `vault.deallocate(adapter, affectedMarketData, assets)` example in that section. ```solidity // Deallocate from the affected market into vault idle assets vault.deallocate(adapter, affectedMarketData, assets); // Optionally reallocate into a healthy market on the same adapter vault.allocate(adapter, healthyMarketData, assets); ``` This begins exiting the position without first adding more withdrawable liquidity at an inflated share price. If the market is illiquid, repeat as liquidity becomes available. The action is reversible: raise `maxRate` back to a non-zero value once the unsafe exposure has been removed and the share price again reflects only recoverable assets. *** ## Unsafe Adapter: Soft Deprecation [#unsafe-adapter-soft-deprecation] Use this procedure when an adapter is deemed too risky for continued allocation, but all contracts are still functioning correctly and assets can be withdrawn normally. If the adapter is working properly and the markets are deemed risky for allocation, the removal should happen [at the market level](/curate/tutorials-v2/emergency/#unsafe-market-soft-deprecation), not at the adapter level. **Do not submit `removeAdapter` until all caps are at zero and all assets are deallocated.** First, zero all caps for the adapter's IDs so that during the timelock window any `allocate` call will revert (`ZeroAbsoluteCap`), preventing [frontrunning](/curate/concepts/security-considerations/#adapter-removal-frontrunning). Then deallocate all existing assets from the adapter before submitting `removeAdapter`. If you remove an adapter that still holds assets, those assets are **permanently lost**. ### Script procedure [#script-procedure-2] **Step 1 - Unset the Liquidity Adapter (if applicable)** If this adapter is currently set as the vault's `liquidityAdapter`, the `Allocator` must unset it before proceeding. Leaving it set will cause all subsequent deposits into the vault to revert. ```solidity // Pass address(0) to unset the liquidity adapter vault.setLiquidityAdapterAndData(address(0), ""); ``` **Step 2 - Revoke Pending Cap Increases** If there are any pending cap increases for ids associated with the adapter, the `Sentinel` or `Curator` should call `revoke(bytes)` on the vault to cancel them. Both absolute and relative cap increases should be revoked if pending. ```solidity // Revoke a pending absolute cap increase bytes memory pendingAbsoluteData = abi.encodeCall(IVaultV2.increaseAbsoluteCap, (idData, newCap)); vault.revoke(pendingAbsoluteData); // Revoke a pending relative cap increase bytes memory pendingRelativeData = abi.encodeCall(IVaultV2.increaseRelativeCap, (idData, newRelativeCap)); vault.revoke(pendingRelativeData); ``` **Step 3 - Set Caps to Zero** The `Curator` or `Sentinel` calls `decreaseAbsoluteCap` and `decreaseRelativeCap` for all ids associated with the adapter, setting them to `0`. This prevents any new allocations to the adapter. ```solidity vault.decreaseAbsoluteCap(idData, 0); vault.decreaseRelativeCap(idData, 0); ``` **Step 4 - Deallocate Liquidity to Idle** The `Allocator` or `Sentinel` calls `deallocate` on the vault to withdraw all available assets from the adapter back to idle. If the underlying market is illiquid, this may need to be done in stages as liquidity becomes available. ```solidity vault.deallocate(adapter, data, assets); ``` The `assets` parameter requires an exact amount - neither the vault nor the `MorphoMarketV1AdapterV2` adapter interprets `type(uint256).max` as "withdraw all." You must query the current allocation and pass the precise value. **Step 5 - Remove the Adapter** Once the adapter's allocation reaches zero, the `Curator` calls `submit` to propose `removeAdapter`. This action is **timelocked**. After the timelock expires, anyone can execute the removal. ```solidity // Submit removal (timelocked) vault.submit(abi.encodeCall(IVaultV2.removeAdapter, (adapterAddress))); // After timelock expires, execute vault.removeAdapter(adapterAddress); ``` *** ## Market Reverts: Hard Deprecation [#market-reverts-hard-deprecation] Use this procedure when an underlying market is malfunctioning (e.g., its functions consistently revert), making it impossible to withdraw assets normally. **This action involves accepting the loss of any assets stuck in the market.** ### Curator App procedure [#curator-app-procedure-2] Requires `Curator` role. 1. Open the [Curator app](https://curator.morpho.org/vaults), select your Vault V2, and go to the **Emergency** tab. 2. Trigger the "Hard Market Removal" flow with the "Start" button. "Hard Market Removal" permanently abandon capital stuck in a reverting market. Across three transactions, pending cap increases are revoked, caps are zeroed, and the adapter's shares for this market are burned. Lost funds cannot be recovered.
### MorphoMarketV1AdapterV2 - Script procedure [#morphomarketv1adapterv2---script-procedure] The `MorphoMarketV1AdapterV2` has its own independent timelock system. Forced market removal happens at the **adapter level** via `burnShares`, which zeroes out the adapter's internal share accounting for a specific market. **Step 1 - Set Caps to Zero** As with soft deprecation, the `Curator` or `Sentinel` should immediately decrease all caps to zero for ids associated with the market. **Critical: Complete this step before `burnShares` executes**. If the affected market's adapter is set as the vault's `liquidityAdapter`, deposits into the vault will automatically re-allocate to that market, thus recreating the position you just burned. Setting caps to zero blocks this. **Step 2 - Submit `burnShares` (Timelocked at Adapter Level)** The `Curator` calls `submit` on the adapter itself (not the vault) to propose burning the shares for the broken market. ```solidity // Submit to the adapter (not the vault) adapter.submit(abi.encodeCall(IMorphoMarketV1AdapterV2.burnShares, (marketId))); ``` The mandatory waiting period must elapse. During this time, the `Sentinel` or `Curator` can revoke via the adapter's `revoke` function if needed. **Step 3 - Execute `burnShares`** After the adapter timelock expires, anyone can execute the burn. ```solidity adapter.burnShares(marketId); ``` **Step 4 - Sync Vault Accounting** Deallocate `0` from the vault to update its internal allocation tracking. ```solidity vault.deallocate(adapter, data, 0); ``` **Warning: Loss of Assets.** Burnt shares are lost forever. This procedure should only be used when the market consistently reverts and assets are considered irrecoverable. ### MorphoVaultV1Adapter [#morphovaultv1adapter] For the `MorphoVaultV1Adapter`, forced removal happens at the **vault level** since this adapter wraps an entire Vault V1 position rather than individual markets. In this scenario, follow the procedure in [V1 Emergency Actions](/curate/tutorials-v1/emergency/). ### Impact on Depositors [#impact-on-depositors] Morpho Vault V2 implements **automatic loss socialization**: when the adapter's `realAssets()` reports a value lower than previously recorded `totalAssets`, the vault updates its accounting downward, and the loss is reflected proportionally in the vault receipt token price for all depositors. For a detailed breakdown of how this accounting update is computed, see [Handling Bad Debt](/curate/tutorials-v2/bad-debt/). If the underlying adapter wraps a Vault V1.1, note that V1.1 does *not* realize bad debt internally - so the Morpho Vault V2 will also not reflect those specific losses. *** ## Allocator Compromise [#allocator-compromise] If an `Allocator` is compromised or acting maliciously, the Curator should act to remove the role and correct any misallocations. The Allocator can only operate within the bounds set by the Curator (enabled adapters and caps), so the blast radius is limited to misallocation within the allowed set. ### Curator App procedure [#curator-app-procedure-3] Requires `Curator` role. 1. Open the [Curator app](https://curator.morpho.org/vaults), select your Vault V2, and go to the **Emergency** tab. 2. Trigger the "Allocator Compromised" flow with the "Start" button. 3. Select the compromised allocator address and proceed. Remove the compromised allocator and zero all adapter caps. The exact steps depend on the current timelock state. Reversing this requires manually reconfiguring the vault.
### Script procedure [#script-procedure-3] **Step 1 - Remove the Allocator Role** The `Curator` calls `submit` to propose `setIsAllocator(compromisedAddress, false)`. ```solidity vault.submit(abi.encodeCall(IVaultV2.setIsAllocator, (compromisedAddress, false))); // If timelock is 0, execute immediately vault.setIsAllocator(compromisedAddress, false); ``` If the `setIsAllocator` timelock is non-zero, proceed to Step 2 while waiting. **Step 2 - Set Caps to Zero** The `Sentinel` (or `Curator`) should immediately decrease all caps to zero. This is **instant** (cap decreases are exempt from timelocks) and neutralizes the allocator regardless of whether the role removal is timelocked. ```solidity vault.decreaseAbsoluteCap(idData, 0); vault.decreaseRelativeCap(idData, 0); ``` **Step 3 - Correct Misallocations** A trusted Allocator or the Curator should deallocate from any wrongly allocated positions and reallocate according to the vault's intended strategy. ```solidity vault.deallocate(adapter, data, assets); ``` Once the compromised allocator is removed, the Curator re-establishes caps and assigns a new allocator. *** ## Curator Compromise [#curator-compromise] If the `Curator` begins acting maliciously (submitting caps for unsafe adapters or markets, adding untrusted markets or caps, changing fees or gates), the Owner and Sentinel must coordinate a response. ### Curator App procedure [#curator-app-procedure-4] 1. The `Owner` opens the [Curator app](https://curator.morpho.org/vaults), selects the vault, goes to the **Roles** tab, and replaces the Curator with a new trusted address (`setCurator`). This is instant and immediately strips the compromised address of all Curator capabilities.
2. The `Sentinel` opens the `Sentinel` tab and revokes all pending actions submitted by the compromised curator. Use **Batch Action** to cancel multiple proposals at once. More on the [Sentinel page](/curate/tutorials-v2/sentinel/). 3. The new `Curator` reviews any malicious changes that were already executed and addresses them from the **Emergency Actions** tab: zero affected caps, deallocate assets, and submit adapter removal if needed. ### Script procedure [#script-procedure-4] **Step 1 - Owner Replaces the Curator** (Instant) The `Owner` immediately calls `setCurator` to transfer control to a new, trusted address. This is **not timelocked** - the Owner can execute this instantly. ```solidity vault.setCurator(newTrustedCurator); ``` This instantly strips the compromised address of all Curator capabilities. **Step 2 - Sentinel Revokes All Pending Actions** (Instant) The `Sentinel` must call `revoke` on every pending timelocked action submitted by the compromised curator. In V2, `revoke(bytes)` can cancel **any** pending timelocked action - adapter additions, cap increases, fee changes, gate changes, allocator changes, timelock decreases, etc. ```solidity vault.revoke(pendingMaliciousData); ``` **Step 3 - Assess Accepted Malicious Actions** If any malicious changes were executed before they could be revoked: * **Malicious adapter was added:** The new Curator follows the [Soft Deprecation procedure](/curate/tutorials-v2/emergency#unsafe-adapter-soft-deprecation) to safely exit. Set caps to zero, deallocate all assets, then submit `removeAdapter`. * **Caps were increased on unsafe ids:** The new Curator or Sentinel instantly decreases caps to zero. * **Fees were changed:** The new Curator submits corrected fee values (timelocked). * **Gates were changed:** The new Curator submits corrected gate values (timelocked). **Key protection:** Depositors are protected by the timelock window. Even if the Curator is compromised, all risk-increasing actions (adding adapters, increasing caps, changing gates) must go through the timelock, giving the Sentinel and depositors time to react. ### Adapter-Level Remediation (MorphoMarketV1AdapterV2) [#adapter-level-remediation-morphomarketv1adapterv2] If the vault uses `MorphoMarketV1AdapterV2`, the adapter has its own timelock system and checks `IVaultV2(parentVault).curator()` for authorization. Replacing the curator at the vault level automatically revokes the compromised address's access to adapter-level `submit` calls. However, any actions the compromised curator already submitted to the adapter remain pending. The new curator (or Sentinel) should revoke those at the adapter level as well: ```solidity adapter.revoke(pendingAdapterData); ``` *** ## Owner Compromise [#owner-compromise] Owner compromise is the most severe scenario. In Morpho Vault V2, the Owner's powers are intentionally limited - they can **only** set the Curator and Sentinels. They cannot directly modify caps, adapters, fees, or allocations. However, a compromised Owner can appoint a malicious Curator, which cascades into a Curator compromise scenario. Depositors must exit the vault before malicious configuration changes take effect. The vault should be considered **permanently compromised.** ### Curator App procedure [#curator-app-procedure-5] Requires `Sentinel` role. 1. The `Sentinel` (if not yet replaced) opens the [Curator app](https://curator.morpho.org/vaults), goes to the `Sentinel` tab, and immediately revokes all suspicious pending actions. More on the [Sentinel page](/curate/tutorials-v2/sentinel/). 2. The `Sentinel` then opens the **Emergency** tab, sets all caps to `0`, and deallocates all assets to idle. 3. **Depositors** should withdraw from the vault interface before any malicious timelocked actions expire. The timelock window is depositors' primary defense. 4. If idle liquidity is insufficient for withdrawals, any user can trigger **Force Deallocate** from the **Emergency Actions** tab to move assets from adapters to idle, then withdraw normally. ### Script procedure [#script-procedure-5] **Step 1 - Sentinel Blocks Immediate Damage** (Instant) If the compromised Owner sets a malicious Curator who begins submitting harmful proposals, the `Sentinel` (if not yet replaced) can revoke all pending actions. The Sentinel should also proactively: * Decrease all caps to zero (preventing new allocations). * Deallocate all assets to idle (moving assets to the safest position). **Step 2 - Depositors Exit Within the Timelock Window** All depositors should withdraw their assets before any malicious timelocked actions expire. The timelock is the depositors' primary defense. **Step 3 - Last Resort: `forceDeallocate`** If the vault lacks idle liquidity for normal withdrawals, depositors can use the **permissionless** `forceDeallocate` to move assets from adapters to idle, then withdraw. **Warning: Full Loss of Vault.** Once the Owner is compromised and all depositors have exited, the vault should be considered permanently compromised. A new vault must be deployed. *** ## Sentinel Compromise [#sentinel-compromise] Sentinel compromise is the **least severe** role compromise, because the Sentinel can only perform risk-*reducing* actions: revoking pending actions, decreasing caps, and deallocating. ### Impact [#impact] A compromised Sentinel could: * Revoke legitimate pending proposals (denial of service, not loss of assets). * Decrease caps to zero (forces vault to idle, no loss of assets). * Deallocate assets to idle (moves everything to idle, no loss of assets). ### Curator App procedure [#curator-app-procedure-6] 1. The `Owner` opens the [Curator app](https://curator.morpho.org/vaults), selects the vault, and goes to the **Roles** tab. 2. Remove the compromised Sentinel.
3. The `Curator` re-submits any legitimate proposals that were revoked by the compromised sentinel. ### Script procedure [#script-procedure-6] The `Owner` calls `setIsSentinel(compromisedAddress, false)` to remove the compromised sentinel and appoint a new one. ```solidity vault.setIsSentinel(compromisedAddress, false); vault.setIsSentinel(newSentinel, true); ``` The Curator then re-submits any legitimate proposals that were revoked by the compromised sentinel. --- # Liquidity Curation with MarketV1AdapterV2 Source: https://docs.morpho.org/curate/tutorials-v2/liquidity-curation ## How the Liquidity Adapter Works [#how-the-liquidity-adapter-works] When a `MorphoMarketV1AdapterV2` is set as the vault's `liquidityAdapter`, it points to **one specific underlying Variable Rate Market** at a time, configured via `liquidityData`. All deposits flow into this market, and all withdrawals are sourced from it. This means: * If the adapter is deployed across multiple Variable Rate Markets (e.g., cbBTC/USDC, WETH/USDC, WBTC/USDC), only the one selected as the liquidity adapter is used for deposits and withdrawals. * On Morpho Vault V2 withdrawals, the vault's idle assets are used first, and then the liquidity adapter's available liquidity. * If that specific market reaches 100% utilization, and the vault's idle assets are empty, the vault is effectively illiquid from the user's perspective. Any withdrawal attempt will revert. * There is **no automatic fallthrough** to other markets within the same adapter. There is no configurable withdrawal queue in Morpho Vault V2 - unlike Vault V1, where the allocator can define and reorder the queue. * If the liquidity adapter's allocation caps are reached, deposit and mint operations will **revert**. There is no fallback to idle assets for deposits. **Key difference from Morpho Vault V1:** In Morpho Vault V1, the withdraw queue iterates through multiple markets to fulfill a withdrawal. In Morpho Vault V2 with a liquidity adapter sourcing via a MarketV1AdapterV2, liquidity is sourced from a single Variable Rate Market at a time. ## Dynamic Reallocation [#dynamic-reallocation] A good practice to maintain liquidity is to run a **reallocation bot** that continuously manages the liquidity adapter configuration: 1. **Monitor** the available liquidity of the current liquidity adapter. 2. **Switch** the liquidity adapter to the most liquid Variable Rate Market when needed, or **reallocate** assets after a large withdrawal or when available liquidity drops below a defined threshold. Both the `liquidityAdapter` and the underlying `liquidityData` (i.e. which Variable Rate Market is used) can be changed on the fly by the allocator bot to meet demand. This is conceptually similar to how Morpho Vaults V1 already work: you need to reallocate liquidity between withdrawal and deposit events to avoid allocation drift. Use a `MorphoMarketV1AdapterV2` as the liquidity adapter. While setting up your reallocation bot, select a sufficiently liquid Morpho Variable Rate Market through `liquidityData`. The bot can later switch that market or reallocate liquidity as conditions change. ## The `forceDeallocate` Safety Net [#the-forcedeallocate-safety-net] Even if the reallocation bot has not caught up, users have a way out. The `forceDeallocate` mechanism allows **anyone** to permissionlessly trigger available liquidity movement from an adapter into the vault's idle assets, thus making these idle assets available for withdrawals. ### Zero-Fee `forceDeallocate` [#zero-fee-forcedeallocate] Setting the `forceDeallocatePenalty` to zero on the Morpho Vault V2 for the `MorphoMarketV1AdapterV2` adapter allows users to trigger reallocation at no cost. This effectively makes all vault available liquidity accessible for withdrawals at all times. **Important:** The `forceDeallocatePenalty` is configured at Morpho Vault V2 level **per adapter**, not globally. You can set it to zero for the `MorphoMarketV1AdapterV2` without affecting other adapters in the vault. ## Griefing Risk with Zero-Fee `forceDeallocate` [#griefing-risk-with-zero-fee-forcedeallocate] Setting the penalty to zero introduces a griefing vector: repeated `forceDeallocate` calls can move available adapter liquidity into the vault's idle assets even when that conflicts with the curator's intended allocation. Each call can undo the allocator bot's latest distribution and force the bot to spend additional transactions restoring it. After a griefing event, the allocator bot would need to reallocate assets back to the intended distribution. ### Mitigation [#mitigation] This is a fundamental tradeoff of making all liquidity available at all times. To reduce the impact of griefing: * Ensure the reallocation bot can respond quickly to restore the intended allocation. * Monitor for repeated `forceDeallocate` calls and adjust the penalty if griefing becomes persistent. ## Summary [#summary] | Aspect | Morpho Vault V1 | Morpho Vault V2 with MarketV1AdapterV2 | | ------------------ | ------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | Withdrawal source | Iterates through withdraw queue across multiple markets | Single liquidity market at a time | | Liquidity curation | Manual or bot-driven reallocation | Bot dynamically reallocates liquidity via `liquidityData` or changes the liquidity adapter | | Fallback mechanism | Queue iteration | `forceDeallocate` (permissionless) | | Gas cost | Higher per tx | Lower per tx | ## Good Practices [#good-practices] 1. **Use `MorphoMarketV1AdapterV2`** as your liquidity adapter for lower gas costs. 2. **Run a reallocation bot** that monitors liquidity and dynamically switch the liquidity adapter as conditions change. 3. **Set `forceDeallocatePenalty` to zero** on the Morpho Vault V2 for the `MorphoMarketV1AdapterV2` adapter to ensure withdrawals are never stuck. 4. **Monitor for allocation drift**. As with Morpho Vault V1, the allocator should reallocate to maintain utilization and risk parameters. --- # Curate Adapters (Vaults V2) Source: https://docs.morpho.org/curate/tutorials-v2/listing-adapters Adapters are the core of a Morpho Vault V2's extensibility. They are smart contracts that act as bridges, allowing your vault to connect to and allocate assets in any version of the Morpho protocol. As a `Curator`, enabling and configuring adapters is how you define your vault's allocation universe. This guide will walk you through the process of deploying, listing, and configuring adapters, with a focus on the **`MorphoMarketV1AdapterV2`** that connects your Morpho Vault V2 directly to Morpho Variable Rate Markets. It also covers configuring adapter caps after deployment. **What is an Adapter?** An adapter contains the specific logic needed to `allocate` and `deallocate` assets to a target protocol. It must be enabled via `addAdapter` (a **timelocked** action) before the vault can interact with it. Adapters also define the "ids" used for risk management caps. ## Setting Up the `MorphoMarketV1AdapterV2` [#setting-up-the-morphomarketv1adapterv2] The `MorphoMarketV1AdapterV2` allows your Morpho Vault V2 to allocate directly to variable rate markets. It is deployed via the `MorphoMarketV1AdapterV2Factory`, which takes the parent vault address and automatically connects to the Morpho Blue contract. ### Method 1: via the Curator App V2 (Recommended) [#method-1-via-the-curator-app-v2-recommended] [curator.morpho.org](https://curator.morpho.org/vaults) On your Morpho Vault V2 page, navigate to the **Adapters** tab. If no adapter is configured yet, the interface will guide you through the setup flow.
The flow will implement the steps below: * Deploy adapter * Submit adapter * Approve adapter timelock (if any) * Set adapter caps Before allocating, configure the required collateral-token and market caps in [(De)List Markets](/curate/tutorials-v2/market-listing/). ### Method 2: via Script [#method-2-via-script] The deployment repository handles adapter setup automatically: **[https://github.com/morpho-org/vault-v2-deployment](https://github.com/morpho-org/vault-v2-deployment)** This repository is for educational purposes only. Do not use in production without thorough review and testing. For adding adapters after deployment: **`script/AddAdapter.s.sol`** ```solidity pragma solidity 0.8.28; import {Script, console} from "forge-std/Script.sol"; import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol"; import {IMorphoMarketV1AdapterV2Factory} from "vault-v2/adapters/interfaces/IMorphoMarketV1AdapterV2Factory.sol"; contract AddAdapter is Script { function run() external { address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS"); address factoryAddress = vm.envAddress("MORPHO_MARKET_V1_ADAPTER_V2_FACTORY"); IVaultV2 vault = IVaultV2(vaultAddress); vm.startBroadcast(); // Deploy new adapter via factory address newAdapter = IMorphoMarketV1AdapterV2Factory(factoryAddress) .createMorphoMarketV1AdapterV2(vaultAddress); // Submit enablement proposal bytes memory enableData = abi.encodeCall(IVaultV2.addAdapter, (newAdapter)); vault.submit(enableData); // Submit cap increases bytes memory idData = abi.encode("this", newAdapter); vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (idData, type(uint128).max))); vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (idData, 1e18))); vm.stopBroadcast(); console.log("Adapter deployed at:", newAdapter); console.log("Proposals submitted. Execute after timelock."); } } ``` ### Method 3: via Etherscan [#method-3-via-etherscan] **1. Deploy the Adapter** Deploy `MorphoMarketV1AdapterV2` via its factory by calling: ```solidity factory.createMorphoMarketV1AdapterV2(vaultV2Address) ``` where `factory` is the deployed `MorphoMarketV1AdapterV2Factory` contract. **2. Enable the Adapter** 1. As Curator, encode: `abi.encodeCall(IVaultV2.addAdapter, (adapterAddress))`. 2. Call `submit(bytes)` with the encoded data. 3. After timelock, call `addAdapter(adapterAddress)`. **3. Set Risk Caps** The adapter's ID is computed as: ```solidity bytes memory idData = abi.encode("this", adapterAddress); bytes32 id = keccak256(idData); ``` Submit and execute cap increases: * `increaseAbsoluteCap(idData, capAmount)` * `increaseRelativeCap(idData, relativeCapWad)` (where 1e18 = 100%) **4. Set as Liquidity Adapter (if needed)** As Allocator, call with the default `MarketParams` encoded as data. The encoded market params identify which Morpho Variable Rate Market will receive new user deposits and serve withdrawals: ```solidity bytes memory liquidityData = abi.encode(marketParams); vault.setLiquidityAdapterAndData(adapterAddress, liquidityData); ``` ## Configuring Adapter Caps [#configuring-adapter-caps] ### Method 1: via the Curator App V2 (Recommended) [#method-1-via-the-curator-app-v2-recommended-1] [curator.morpho.org](https://curator.morpho.org/vaults) Directly on the **Caps** page of your Morpho Vault V2 by clicking on the "Edit Caps" button.
Cap increases with a non-zero timelock are submitted as pending actions. Once the timelock has elapsed, execute them from the vault's **Timelocks** page. If the timelock is zero, the Curator App submits and executes the cap changes in the same transaction. Cap decreases take effect immediately. ### Method 2: via Script [#method-2-via-script-1] Each adapter has associated IDs for risk management: * **MorphoMarketV1AdapterV2**: Three IDs: * Adapter ID: `keccak256(abi.encode("this", adapterAddress))` * Collateral ID: `keccak256(abi.encode("collateralToken", collateralAddress))` * Market ID: `keccak256(abi.encode("this/marketParams", address(this), marketParams))` - where `address(this)` is the deployed adapter address * **MorphoVaultV1Adapter**: Single ID based on `keccak256(abi.encode("this", adapterAddress))` Sentinels can decrease caps immediately: ```solidity vault.decreaseAbsoluteCap(idData, newLowerCap); vault.decreaseRelativeCap(idData, newLowerRelativeCap); ``` ## Delisting an Adapter [#delisting-an-adapter] **Critical: Set Caps to Zero Before Removing an Adapter** Before delisting an adapter, you **must** set both the absolute cap and relative cap to `0` for every ID exclusive to that adapter. This prevents any allocator from frontrunning the timelocked `removeAdapter` call by allocating assets into the adapter during the timelock window. Without this step, an allocator could push assets into the adapter right before removal, and since `removeAdapter` does **not** revert if the adapter still holds assets, those assets would be permanently lost to the vault's depositors. Before delisting, follow the full [Adapter Soft Deprecation procedure](/curate/tutorials-v2/emergency/#unsafe-adapter-soft-deprecation), which covers both deallocating existing assets and zeroing caps to prevent [frontrunning](/curate/concepts/security-considerations/#adapter-removal-frontrunning). ## Setting Force Deallocate Penalty [#setting-force-deallocate-penalty] To enable emergency withdrawals with a penalty: ```solidity // As Curator, submit proposal (max 2% = 0.02e18) vault.submit(abi.encodeCall(IVaultV2.setForceDeallocatePenalty, (adapterAddress, 0.01e18))); // After timelock vault.setForceDeallocatePenalty(adapterAddress, 0.01e18); // 1% penalty ``` --- # (De)List Markets (in MorphoMarketV1AdapterV2) Source: https://docs.morpho.org/curate/tutorials-v2/market-listing Once your [`MorphoMarketV1AdapterV2` is deployed and enabled](/curate/tutorials-v2/listing-adapters/), you can control which Morpho Variable Rate markets it may allocate to by setting per-market risk caps. Listing a market means setting non-zero caps for it and allocating assets to it. Delisting means removing those caps and withdrawing all allocated assets. **Cap hierarchy in `MorphoMarketV1AdapterV2`** Three layers of caps (absolute/relative) gate every allocation: 1. **Adapter cap** - the ceiling for the whole adapter. 2. **Collateral token cap** - the ceiling across all markets sharing the same collateral asset. 3. **Market cap** - the ceiling for one specific `MarketParams` tuple. All three must be non-zero for the vault to allocate to a given market. Cap increases are timelocked; decreases are instant and can also be done by a Sentinel. The adapter cap (layer 1) is set during adapter setup - see [Curate Adapters](/curate/tutorials-v2/listing-adapters/) for details. This guide covers layers 2 and 3. ## Listing a Market [#listing-a-market] ### Method 1: via the Curator App V2 (Recommended) [#method-1-via-the-curator-app-v2-recommended] [curator.morpho.org](https://curator.morpho.org/vaults) On your Morpho Vault V2 page, open the **Caps** tab. * Click **"Add collateral"** to set the two collateral token caps (absolute and relative), or skip if the collateral is already listed. * Click **"Add market"** to set the two market caps (absolute and relative) for the specific market.
For each cap, the process is two steps: 1. Submit the cap proposal. 2. Once the timelock has elapsed, click **"Timelocks"** to accept it. You can submit all cap proposals first and then batch-accept them all at once via the **"Timelocks"** button once the timelock has passed. The market will be listed in the `MorphoMarketV1AdapterV2` once you have allocated some assets. ### Method 2: via Script [#method-2-via-script] This snippet is for educational purposes only. Do not use in production without thorough review and testing. The snippet does the following: * Computes the `idData` encoding for the collateral token cap and the market cap * **Step 1**: submits four timelocked cap proposals (absolute + relative for collateral, absolute + relative for market) * **Step 2** (separate transaction, after timelock): accepts all four cap proposals by calling the functions directly The ID data encoding expected by `MorphoMarketV1AdapterV2` for each cap level is: | Cap level | `idData` encoding | | ---------------- | --------------------------------------------------------------- | | Collateral token | `abi.encode("collateralToken", collateralTokenAddress)` | | Market | `abi.encode("this/marketParams", adapterAddress, marketParams)` | ```solidity // idData for each cap level - passed directly to cap functions (hashed internally by the vault) bytes memory collateralIdData = abi.encode("collateralToken", collateralToken); bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams); // --- Step 1: Submit proposals (run immediately) --- vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (collateralIdData, type(uint128).max))); vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (collateralIdData, 1e18))); vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (marketIdData, absoluteCap))); vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (marketIdData, relativeCap))); // --- Step 2: Execute after the vault's timelock has elapsed (run in a separate transaction) --- vault.increaseAbsoluteCap(collateralIdData, type(uint128).max); vault.increaseRelativeCap(collateralIdData, 1e18); vault.increaseAbsoluteCap(marketIdData, absoluteCap); vault.increaseRelativeCap(marketIdData, relativeCap); ``` ### Method 3: via Etherscan [#method-3-via-etherscan] ### 1. Prepare the cap ID data [#1-prepare-the-cap-id-data] The `idData` bytes are passed directly to the cap functions (the vault hashes them internally): ```solidity bytes memory collateralIdData = abi.encode("collateralToken", collateralTokenAddress); bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams); ``` ### 2. Submit collateral token caps [#2-submit-collateral-token-caps] As Curator, call `submit(bytes)` on the vault with: * `abi.encodeCall(IVaultV2.increaseAbsoluteCap, (collateralIdData, capAmount))` * `abi.encodeCall(IVaultV2.increaseRelativeCap, (collateralIdData, relativeCapWad))` (where `1e18` = 100%) ### 3. Submit market caps [#3-submit-market-caps] Call `submit(bytes)` again with: * `abi.encodeCall(IVaultV2.increaseAbsoluteCap, (marketIdData, capAmount))` * `abi.encodeCall(IVaultV2.increaseRelativeCap, (marketIdData, relativeCapWad))` (where `1e18` = 100%) ### 4. Accept after timelock [#4-accept-after-timelock] Once the timelock has elapsed, call the functions directly: * `increaseAbsoluteCap(collateralIdData, capAmount)` * `increaseRelativeCap(collateralIdData, relativeCapWad)` (where `1e18` = 100%) * `increaseAbsoluteCap(marketIdData, capAmount)` * `increaseRelativeCap(marketIdData, relativeCapWad)` (where `1e18` = 100%) ## Delisting a Market [#delisting-a-market] A market is considered fully delisted when its caps are `0` **and** its allocation is `0`. Both steps are independent: setting caps to zero is safe even while assets are still allocated (it just prevents new allocations), and you can deallocate at any time while caps are non-zero. The typical order is to cap first, then deallocate when liquidity allows. ### Method 1: via the Curator App V2 (Recommended) [#method-1-via-the-curator-app-v2-recommended-1] [curator.morpho.org](https://curator.morpho.org/vaults) ### 1. Reduce caps to zero [#1-reduce-caps-to-zero] On the **Caps** tab, click **"Edit Caps"** and set both the absolute and relative caps for the market (and optionally the collateral token, if no other market uses it) to `0`. Cap decreases are instant and do not require a timelock, they can also be done by a Sentinel.
Set both the Absolute Cap and Relative Cap fields to `0` and confirm the transaction.
### 2. Deallocate assets from the market [#2-deallocate-assets-from-the-market] On the **Allocation** tab, move all remaining assets out of the target market. If the market is not immediately liquid (highly utilized), the recommended approach is to monitor available liquidity and run a bot that deallocates (or reallocates to another market) as liquidity becomes available, rather than waiting to do it manually. See [Liquidity Curation](/curate/tutorials-v2/liquidity-curation/) for more on bot-driven reallocation patterns. **Deallocate dust.** Due to Morpho Blue share-to-asset rounding, `vault.allocation()` may retain a small residual amount (a few wei) after a full deallocate. This does not affect the delisting — with caps set to zero, no further allocation is possible and the market is effectively delisted.
### Method 2: via Script [#method-2-via-script-1] This snippet is for educational purposes only. Do not use in production without thorough review and testing. The script does the following: * Reads vault, adapter, and market parameters from environment variables * Computes the `idData` encodings for the market cap and the collateral token cap * **Step 1**: immediately decreases both market caps to zero (no timelock, Sentinel can also do this); optionally also zeroes the collateral token caps if no other listed market shares the same collateral * **Step 2**: reads the current allocation via `vault.allocation(keccak256(marketIdData))` and deallocates if non-zero, the market must have available liquidity for this to succeed **`script/DelistMarket.s.sol`** ```solidity pragma solidity 0.8.28; import {Script, console} from "forge-std/Script.sol"; import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol"; import {MarketParams} from "morpho-blue/interfaces/IMorpho.sol"; contract DelistMarket is Script { function run() external { address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS"); address adapterAddress = vm.envAddress("MORPHO_MARKET_V1_ADAPTER_V2"); address collateralToken = vm.envAddress("COLLATERAL_TOKEN"); MarketParams memory marketParams = MarketParams({ loanToken: vm.envAddress("LOAN_TOKEN"), collateralToken: collateralToken, oracle: vm.envAddress("ORACLE"), irm: vm.envAddress("IRM"), lltv: vm.envUint("LLTV") }); IVaultV2 vault = IVaultV2(vaultAddress); // marketData: raw encoding passed to allocate/deallocate calls on the adapter // marketIdData: encoding used as the cap key (hashed internally by the vault) bytes memory marketData = abi.encode(marketParams); bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams); bytes memory collateralIdData = abi.encode("collateralToken", collateralToken); vm.startBroadcast(); // Step 1: decrease market caps to zero (instant, no timelock - Sentinel can also do this) vault.decreaseAbsoluteCap(marketIdData, 0); vault.decreaseRelativeCap(marketIdData, 0); // Optional: clear collateral token caps only if no other listed market uses this collateral. // Remove the two lines below if other markets share the same collateral asset. vault.decreaseAbsoluteCap(collateralIdData, 0); vault.decreaseRelativeCap(collateralIdData, 0); // Step 2: deallocate remaining assets (requires available liquidity in the market) // vault.allocation() is keyed on keccak256(marketIdData) - same encoding as the cap ID uint256 allocated = vault.allocation(keccak256(marketIdData)); if (allocated > 0) { vault.deallocate(adapterAddress, marketData, allocated); } vm.stopBroadcast(); console.log("Market delisted successfully."); } } ``` **Deallocate dust.** Due to Morpho Blue share-to-asset rounding, `vault.allocation()` may retain a small residual amount (a few wei) after a full deallocate. This does not affect the delisting — with caps set to zero, no further allocation is possible and the market is effectively delisted. ### Method 3: via Etherscan [#method-3-via-etherscan-1] ### 1. Prepare the cap ID data [#1-prepare-the-cap-id-data-1] ```solidity bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams); bytes memory collateralIdData = abi.encode("collateralToken", collateralTokenAddress); ``` ### 2. Decrease market caps to zero [#2-decrease-market-caps-to-zero] As Curator or Sentinel, call directly (no timelock required for decreases): * `decreaseAbsoluteCap(marketIdData, 0)` * `decreaseRelativeCap(marketIdData, 0)` ### 3. (Optional) Decrease collateral token caps to zero [#3-optional-decrease-collateral-token-caps-to-zero] Only if no other listed market uses the same collateral asset: * `decreaseAbsoluteCap(collateralIdData, 0)` * `decreaseRelativeCap(collateralIdData, 0)` ### 4. Deallocate remaining assets [#4-deallocate-remaining-assets] As Allocator, call `deallocate(adapterAddress, marketData, amount)` where `marketData = abi.encode(marketParams)`. **Deallocate dust.** Due to Morpho Blue share-to-asset rounding, `vault.allocation()` may retain a small residual amount (a few wei) after a full deallocate. This does not affect the delisting — with caps set to zero, no further allocation is possible and the market is effectively delisted. ## Allocating to a Listed Market [#allocating-to-a-listed-market] Once caps are accepted, the Allocator can direct assets to the market via the [Curator App V2](https://curator.morpho.org/vaults). On your Morpho Vault V2 page, open the **Allocation** tab and click **"Reallocate"**.
Enter the desired allocation amount for each market in the **Allocation** column, then click **"Save"** to confirm.
To set this market as the vault's liquidity source for user deposits and withdrawals, see [Liquidity Curation](/curate/tutorials-v2/liquidity-curation/). --- # Configure Vault V2 Roles Source: https://docs.morpho.org/curate/tutorials-v2/roles This tutorial guides you through setting up and managing roles for a **Morpho Vault V2**. A clear understanding and proper assignment of roles are critical for the vault's security, governance, and operational success. **Separation of Concerns is Key** The Vault V2 role system is designed with a strict separation of powers. The `Owner` manages permissions, the `Curator` sets strategy, the `Allocator` executes it, and the `Sentinel` ensures safety. Unlike V1, roles do not inherit permissions from each other. For a detailed breakdown, please refer to the [Vaults V2 Roles concept page](/curate/concepts/roles#morpho-vaults-v2-roles). **Handle Private Keys with Extreme Care** The `Owner` and `Curator` roles hold significant power. It is highly recommended to secure these roles with a multisig or an institutional-grade MPC wallet. Never use a single EOA (hot wallet) for these roles in a production environment. Always verify addresses before executing transactions. ## The Role Setup Workflow [#the-role-setup-workflow] Setting up roles for a new Vault V2 follows a specific sequence. The `Owner` first assigns the `Curator` and `Sentinels` (actions which are immediate). Then, the `Curator` proposes strategic changes, such as appointing an `Allocator`. Crucially, most of the `Curator`'s actions are **timelocked**. The process for these actions depends on whether a timelock duration is set. ## Managing Roles via the Curator App [#managing-roles-via-the-curator-app] The [Curator app](https://curator.morpho.org/vaults) exposes a UI for Vault V2 role setup and role management. Use it when you prefer a guided flow; the script and Etherscan flows below remain useful for repeatable deployments and low-level verification. 1. Open the app and select your vault. 2. Connect the wallet that controls the required role: * `Owner` for appointing the `Curator` or adding and removing `Sentinels`. * `Curator` for submitting timelocked role changes such as adding or removing `Allocators`. * `Sentinel` for revoking pending actions from the [Sentinel tab](/curate/tutorials-v2/sentinel/). * **Any address** for executing an already-submitted action after its timelock expires. 3. Open the **Roles** tab and review the current `Owner`, `Curator`, `Allocators`, and `Sentinels`. 4. For immediate `Owner` actions, fill the new address, verify it carefully, and sign the transaction. 5. For timelocked `Curator` actions, submit the proposal, wait until it becomes executable, then return to execute it. A Curator or Sentinel can revoke the pending proposal before execution. The Curator app does not bypass protocol permissions or timelocks. Always verify the target vault, role address, and action before signing.
## Managing Roles via Scripts [#managing-roles-via-scripts] The official deployment repository handles role management efficiently: **[https://github.com/morpho-org/vault-v2-deployment](https://github.com/morpho-org/vault-v2-deployment)** ### Scenario 1: Initial Vault Setup (Zero Timelock) [#scenario-1-initial-vault-setup-zero-timelock] The `DeployVaultV2.s.sol` script handles the complete initial setup. Here's how it manages roles: ```solidity // The script sets the broadcaster as temporary owner and curator VaultV2 vaultV2 = new VaultV2(broadcaster, vaultV1.asset()); vaultV2.setCurator(broadcaster); // Submit allocator role assignments vaultV2.submit(abi.encodeCall(vaultV2.setIsAllocator, (broadcaster, true))); vaultV2.submit(abi.encodeCall(vaultV2.setIsAllocator, (allocator, true))); // Execute immediately (timelock is 0 by default) vaultV2.setIsAllocator(broadcaster, true); vaultV2.setIsAllocator(allocator, true); // After configuration, transfer roles to final addresses vaultV2.setCurator(curator); if (sentinel != address(0)) { vaultV2.setIsSentinel(sentinel, true); } vaultV2.setOwner(owner); ``` ### Scenario 2: Production Management (Non-Zero Timelock) [#scenario-2-production-management-non-zero-timelock] Once a vault has non-zero timelocks, role changes require a two-step process: #### Step 1: Submitting a Role Change [#step-1-submitting-a-role-change] Create a script to submit the proposal: **`script/SubmitRoleChange.s.sol`** ```solidity pragma solidity 0.8.28; import {Script} from "forge-std/Script.sol"; import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol"; contract SubmitRoleChange is Script { function run() external { address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS"); address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS"); IVaultV2 vault = IVaultV2(vaultAddress); vm.startBroadcast(); // Submit the proposal bytes memory data = abi.encodeCall(IVaultV2.setIsAllocator, (newAllocator, true)); vault.submit(data); vm.stopBroadcast(); uint256 timelock = vault.timelock(IVaultV2.setIsAllocator.selector); console.log("Proposal submitted. Execute after", timelock, "seconds"); } } ``` #### Step 2: Executing the Role Change [#step-2-executing-the-role-change] After the timelock expires, execute the change: **`script/ExecuteRoleChange.s.sol`** ```solidity pragma solidity 0.8.28; import {Script} from "forge-std/Script.sol"; import {IVaultV2} from "vault-v2/interfaces/IVaultV2.sol"; contract ExecuteRoleChange is Script { function run() external { address vaultAddress = vm.envAddress("VAULT_V2_ADDRESS"); address newAllocator = vm.envAddress("NEW_ALLOCATOR_ADDRESS"); IVaultV2 vault = IVaultV2(vaultAddress); vm.startBroadcast(); // Execute the timelocked action vault.setIsAllocator(newAllocator, true); vm.stopBroadcast(); console.log("Allocator role assigned successfully"); } } ``` ### Revoking Proposals [#revoking-proposals] Curators and Sentinels can revoke pending proposals: ```solidity // To revoke a pending proposal bytes memory dataToRevoke = abi.encodeCall(IVaultV2.setIsAllocator, (address, bool)); vault.revoke(dataToRevoke); ``` ## Managing Roles via Etherscan [#managing-roles-via-etherscan] For manual management, the process mirrors the scripted approach. ### Owner Actions (Immediate) [#owner-actions-immediate] As the `Owner`, connect your wallet to the Vault V2 contract on Etherscan and use the `Write Contract` tab to: * Execute `setCurator(address newCurator)` to appoint the Curator. * Execute `setIsSentinel(address account, bool newIsSentinel)` to add or remove Sentinels. * Execute `setOwner(address newOwner)` to transfer ownership. ### Curator Actions (Timelocked) [#curator-actions-timelocked] As the `Curator`, connect your wallet to: 1. **Submit the Proposal**: * Use a tool like [Foundry's `cast calldata`](https://book.getfoundry.sh/reference/cast/cast-calldata) to ABI-encode your intended function call (e.g., `setIsAllocator(0x..., true)`). * Call the `submit(bytes)` function with the encoded data. 2. **Wait for the Timelock**: Check `executableAt(bytes data)` to see when execution is allowed. 3. **Execute the Proposal**: After the timelock, **anyone** can call the original function to finalize the change. ### Checking Role Status [#checking-role-status] Use the `Read Contract` tab to verify current roles: * `owner()`: Returns the current owner * `curator()`: Returns the current curator * `isAllocator(address)`: Check if an address is an allocator * `isSentinel(address)`: Check if an address is a sentinel --- # Act as a Sentinel in the Curator App Source: https://docs.morpho.org/curate/tutorials-v2/sentinel A `Sentinel` is a risk-mitigation role for Morpho Vault V2. It can react quickly to reduce vault risk, but it cannot introduce new risk. The role can revoke pending timelocked actions, decrease caps, and deallocate assets back to the vault's idle balance. Use the [Curator app](https://curator.morpho.org/vaults) to perform these actions from a dedicated `Sentinel` tab on each Morpho Vault V2 page. The Curator app does not bypass protocol permissions or timelocks. It prepares the same onchain transactions described in the contract-level docs. ## Before You Start [#before-you-start] * Open the [Curator app](https://curator.morpho.org/vaults) and select the Vault V2 you want to monitor. * Connect the wallet that has the Sentinel role for that vault. * If your Sentinel is a Safe, MPC wallet, or another smart account, make sure the transaction is sent from that exact Sentinel address. ## What a Sentinel Can Do [#what-a-sentinel-can-do] | Action | Effect | Timelocked? | | ------------------------ | -------------------------------------------------------------------------------------------------------------- | ----------- | | Revoke a pending action | Cancels a submitted timelocked action before it is executed | No | | Decrease an absolute cap | Reduces the maximum assets that can be allocated to a risk `id` (Market, Collateral asset, Adapter) | No | | Decrease a relative cap | Reduces the maximum % of vault assets that can be allocated to a risk `id` (Market, Collateral asset, Adapter) | No | | Deallocate | Moves assets from an adapter back to the vault's idle balance | No | A Sentinel **cannot** add adapters, increase caps, allocate assets into adapters, change roles, change fees, change gates, change timelocks, or transfer ownership. ## Emmergency Sentinel Flows [#emmergency-sentinel-flows] The emergency flows below can be triggered by the Sentinel role on the Curator App **Emergency** tab. * **Close Deposits**: Block new deposits by disabling the active liquidity adapter - either zeroing its caps or redirecting it to an idle market. * **Safe Market Removal**: revoke pending cap increases for a market, zero its caps, and safely deallocate any withdrawable funds. * **Sentinel Lockdown**: Execute a full lockdown of the vault, activating all sentinel capabilities.
## Common Sentinel Flows [#common-sentinel-flows] The Sentinel common flows are available on the `Sentinel` tab of your vault. ### Revoke a Pending Action [#revoke-a-pending-action] Use this when a pending Curator action should not be allowed to execute, for example an unsafe cap increase, adapter addition, fee change, gate change, allocator change, or timelock change. 1. In the `Sentinel` tab, review pending actions for the vault. 2. Check the target function, parameters, and executable time.
3. Click on Batch Action and select the action(s) to revoke. You can select several pending changes to revoke it at once. 4. Sign the revoke transaction. After the transaction confirms, verify that the pending action no longer appears in the app and that the corresponding `revoke(bytes)` transaction is visible onchain. The Sentinel can also see all pending changes and revoke it, on any tab, via the tooltip at the bottom right of the app.
### Reduce Exposure to an Unsafe Market or Collateral [#reduce-exposure-to-an-unsafe-market-or-collateral] Use this when a market or all markets sharing a collateral type are considered too risky, but the adapter is still functioning. 1. Identify the risk level 2. Prefer the narrowest safe scope: a market-level for one market, a collateral-level for all markets sharing one collateral (or adapter-level only when the whole adapter should stop receiving liquidity). 3. Set the relevant absolute and relative caps to `0`.
4. Deallocate available assets from the affected market or adapter back to idle by clicking on the "Zero" button and "Save".
5. If the market is illiquid, repeat the deallocation as liquidity becomes available. ### Freeze Risk While a Curator or Owner Issue Is Investigated [#freeze-risk-while-a-curator-or-owner-issue-is-investigated] Use this when the Sentinel needs to create time for depositors, the Owner, or a replacement Curator to react. 1. Revoke every suspicious pending action. 2. Decrease the affected caps to `0`. If the risk is broad or unclear, consider setting all relevant caps to `0` until the situation is assessed. 3. Deallocate assets to idle where possible. 4. Coordinate with the Owner to replace a compromised Curator or Sentinel if needed. 5. Coordinate with the Curator for any follow-up cleanup that requires timelocked actions, such as removing an adapter or re-establishing caps. ## After Each Sentinel Action [#after-each-sentinel-action] * Verify the transaction status in the app and on a block explorer. * Confirm the vault state changed as expected: pending action removed, cap reduced, or allocation moved to idle. * Communicate the action to the Curator and Owner so they can perform any follow-up role, cap, adapter, or liquidity changes. * Continue monitoring until the risk is resolved or the vault is fully unwound. ## Related Docs [#related-docs] * [Roles & Capabilities](/curate/concepts/roles/#sentinel) * [Configure Vault V2 Roles](/curate/tutorials-v2/roles/) * [Emergency Procedures (Vaults V2)](/curate/tutorials-v2/emergency/) * [Liquidity Curation with MarketV1AdapterV2](/curate/tutorials-v2/liquidity-curation/) --- # Unwind a Morpho Vault V2 Source: https://docs.morpho.org/curate/tutorials-v2/unwind-vault-v2 This guide explains how to safely wind down a Morpho Vault V2, ensuring depositors can withdraw their assets while removing market exposure and preventing new capital from entering. Communicate with depositors before starting. Make sure they are aware of the timeline and that yield will drop to zero as assets move to idle. If possible, also warn borrowers in the underlying markets, as withdrawing liquidity increases utilization and can drive up borrow rates. ## Step 1: Prevent New Deposits [#step-1-prevent-new-deposits] In Morpho Vault V2, deposits flow exclusively through the **[liquidity adapter](/curate/tutorials-v2/liquidity-curation/)** (if set). Decreasing its cap to `0` is sufficient to block all new deposits. Cap decreases are **instant** and exempt from timelocks in Morpho Vault V2. See the [Managing Adapters Caps](/curate/tutorials-v2/listing-adapters/#configuring-adapter-caps) tutorial for context on how caps are structured. The `Curator` or `Sentinel` calls `decreaseAbsoluteCap` and `decreaseRelativeCap` for the liquidity adapter id: ```solidity vault.decreaseAbsoluteCap(liquidityAdapterIdData, 0); vault.decreaseRelativeCap(liquidityAdapterIdData, 0); ``` Once the liquidity adapter cap is `0`, incoming deposits will revert. Withdrawals remain fully unaffected. If no liquidity adapter is set, deposits flow directly to idle and there is no cap to zero out. New deposits cannot be blocked at the contract level in that case. If there are any pending cap increases on the liquidity adapter, revoke them first to prevent them from being accepted during the wind-down period. ```solidity bytes memory pendingData = abi.encodeCall(IVaultV2.increaseAbsoluteCap, (liquidityAdapterIdData, newCap)); vault.revoke(pendingData); ``` ## Step 2: Remove Market Exposure [#step-2-remove-market-exposure] The `Allocator` or `Sentinel` calls `deallocate` to withdraw assets from each active market and return them to the vault as **idle assets**. See the [Liquidity Curation](/curate/tutorials-v2/liquidity-curation/) tutorial for more context on how allocations and the liquidity adapter interact. ```solidity vault.deallocate(adapter, marketData, assets); ``` The `assets` parameter requires an exact amount, `type(uint256).max` is not interpreted as "withdraw all." Query the current allocation and pass the precise value before calling. If a market is illiquid (high utilization), this step must be repeated incrementally as liquidity becomes available. Set up monitoring to track when assets can be deallocated and an automation to automatically deallocate assets to idle. ## Step 3: Move Liquidity Adapter Funds to Idle [#step-3-move-liquidity-adapter-funds-to-idle] If the vault uses a **Liquidity Adapter**, the `Allocator` should deallocate all assets from it and move them to pure idle. Pure idle assets have zero yield but are immediately available for withdrawals, which is the priority during a wind-down. ```solidity vault.deallocate(liquidityAdapter, data, assets); ``` Before moving assets to idle, make sure the vault has no active management fee. A management fee accrues continuously against total assets regardless of yield, at 0% yield, it would directly erode depositors' principal over time. Set the management fee to `0` first if needed. ## Step 4: Reduce Yield to Zero [#step-4-reduce-yield-to-zero] Once all assets are held as idle assets in the vault, it yields **0%**. This is the natural outcome of completing Steps 1–3. No additional action is required to suppress yield. ## Optional: Remove Adapters [#optional-remove-adapters] Once all assets have been withdrawn from an adapter and its allocation reaches zero, the `Curator` can formally remove it (more about this process [here](/curate/tutorials-v2/listing-adapters/#delisting-an-adapter)). This is optional during wind-down, an adapter with zero allocation and zero caps poses no risk to depositors. *** ## Optional: For Listed Morpho Vault V2 [#optional-for-listed-morpho-vault-v2] If your vault is **listed on the Morpho app**, reach out to the Morpho Association to coordinate the off-chain wind-down: * **Display a deprecation warning** on the vault's page in the app, informing depositors that the vault is being wound down. * **Remove the vault from the main listing** once deposits are blocked and the wind-down is underway. * **Notify users** through the usual communication channels (governance forum, curator socials). --- # Create a Morpho Vault V2 Source: https://docs.morpho.org/curate/tutorials-v2/vault-creation Deploying a Morpho Vault V2 is a permissionless process that anyone can initiate. This guide covers three methods for deploying your vault, ranging from the simplest user-friendly interface to more advanced technical approaches. This guide covers three methods for deploying your vault: 1. **Using the Curator App (Recommended)**: The easiest and most user-friendly method with a graphical interface for vault creation and configuration. 2. **Using a Deployment Script**: A powerful and efficient method for developers, allowing you to create, configure, and assign roles for your vault in a single, automated process. 3. **Using Etherscan**: A manual approach suitable for those who prefer interacting directly with contracts on a block explorer. ## Method 1: Create a Vault via the Curator App [#method-1-create-a-vault-via-the-curator-app] The Curator App provides the easiest and most user-friendly way to deploy a Morpho Vault V2. This web-based interface guides you through the entire vault creation process without requiring any coding or command-line experience. **Curator App**: [**https://curator.morpho.org/vaults**](https://curator.morpho.org/vaults) ### Why Use the Curator App? [#why-use-the-curator-app] * **No Technical Knowledge Required**: User-friendly graphical interface suitable for all experience levels. * **Guided Setup**: Step-by-step wizard walks you through every configuration option. * **Instant Deployment**: Deploy your vault directly from your browser using your connected wallet. * **Built-in Validation**: Automatic validation of parameters to prevent common configuration mistakes. * **Visual Configuration**: Easily configure roles, adapters, and risk parameters through intuitive forms. ### Getting Started with the Curator App [#getting-started-with-the-curator-app] ### 1. Connect Your Wallet [#1-connect-your-wallet] Navigate to [https://curator.morpho.org/vaults](https://curator.morpho.org/vaults) and connect your Ethereum wallet (e.g., MetaMask, WalletConnect). ### 2. Select Network [#2-select-network] Choose the network where you want to deploy your vault. Morpho Vault V2 is available on multiple networks including Ethereum mainnet, Base, and other supported chains. ### 3. Configure Vault Parameters [#3-configure-vault-parameters] You will first be asked to set the main vault's parameters: * **Asset**: Select the ERC-20 token your vault will use (e.g., USDC, WETH). * **Owner**: Set the initial owner address (ideally a multisig for security). Once the vault created, set and abdicate the [Morpho registry](/curate/concepts/adapter-registry/#the-morpho-registry). You can then set the other Vault's parameters: * **Vault Symbol**: Choose a token symbol for your vault. * **Vault Name**: Choose a descriptive name for your vault. * **Curator**: Assign the curator role for configuring vault parameters. * **Allocator**: Set the allocator address for executing asset allocations. * **Sentinel** (Optional): Optionally assign a sentinel for emergency actions. * **MaxRate**: The maximum rate earn by vault's suppliers. MaxRate must be set for suppliers to earn yield (more [here](/curate/concepts/yield/#interest-rate-controls)). ### 4. Configure Adapters and Risk Parameters [#4-configure-adapters-and-risk-parameters] * Select which Morpho Vault V1 instances you want to use as underlying liquidity markets. * Set initial risk caps (absolute and relative) for each adapter. * Configure the liquidity adapter for handling asset flows. ### 5. Set Timelock (Optional) [#5-set-timelock-optional] Configure timelock durations for critical functions to add an additional security layer for production vaults. ### 6. Review and Deploy [#6-review-and-deploy] Review all your configuration settings, then click "Deploy Vault". Your wallet will prompt you to confirm the transaction. Once deployed, you can continue configuring your vault through the Curator App or use the advanced methods described below for programmatic configuration. ## Method 2: Create and Configure a Morpho Vault V2 via Script [#method-2-create-and-configure-a-morpho-vault-v2-via-script] For developers who prefer a code-based approach, you can refer to the Morpho Vault V2 deployment repository. This repository contains an example script that bundles multiple setup actions into a single transaction. **Repository**: [**https://github.com/morpho-org/vault-v2-deployment**](https://github.com/morpho-org/vault-v2-deployment) **Educational Purpose Only**: This repository and script are provided for **demonstration and educational purposes only**. Do not use in production without thorough review and testing. Always test extensively on testnets before any mainnet deployment. You are solely responsible for understanding every line of code and any consequences of using it. ### Overview [#overview] The `DeployVaultV2WithMarketAdapter.s.sol` script demonstrates how to deploy and configure a Morpho Vault V2. It leverages the fact that new vaults have a zero-second timelock by default, allowing all initial configuration to be executed immediately. This script deploys a Morpho Vault V2 using **MorphoMarketV1AdapterV2**, which connects directly to variable rate markets. The deployment process consists of the following phases: | Phase | Description | | ----- | ------------------------------------------- | | 1 | Deploy Morpho Vault V2 instance via factory | | 2 | Configure temporary permissions | | 3 | Deploy MorphoMarketV1AdapterV2 | | 4 | Submit timelocked configuration changes | | 5 | Execute configuration + gates abdication | | 6 | Set final role assignments | | 7 | Configure market and liquidity adapter | | 8 | Execute vault dead deposit | | 9 | Configure vault timelocks | | 10 | Configure adapter timelocks | ### Prerequisites [#prerequisites] Before deploying, ensure you have: * [Foundry](https://getfoundry.sh/) installed * Git installed * An Ethereum wallet with sufficient assets for deployment * RPC URL for your target network * Sufficient tokens for the dead deposit (required for [inflation attack protection](/curate/concepts/security-considerations/#inflation-front-running-attack-protection)) ### Running the Deployment Script [#running-the-deployment-script] ### 1. Clone the Repository and Install Dependencies [#1-clone-the-repository-and-install-dependencies] ```bash git clone https://github.com/morpho-org/vault-v2-deployment cd vault-v2-deployment forge install ``` ### 2. Configure Environment Variables [#2-configure-environment-variables] Create a `.env` file with your deployment configuration: ```bash # Network RPC_URL=https://... # Your RPC endpoint PRIVATE_KEY=0x... # Your private key (keep secure!) # Role addresses (replace with your actual addresses) OWNER=0x... # Final owner of the vault (ideally a multisig) CURATOR=0x... # Curator address (defaults to OWNER if not set) ALLOCATOR=0x... # Allocator address (defaults to OWNER if not set) SENTINEL=0x... # Sentinel address (optional) # Deployed contract addresses (network-specific) # See https://docs.morpho.org/get-started/resources/addresses/ for addresses ASSET=0x... # Underlying asset token (e.g., USDC, WETH) ADAPTER_REGISTRY=0x... # Morpho Registry address VAULT_V2_FACTORY=0x... # VaultV2 Factory address MORPHO_MARKET_V1_ADAPTER_V2_FACTORY=0x... # MorphoMarketV1AdapterV2 Factory address # First market configuration (optional - set to 0x0 to skip) MARKET_ID=0x... # Morpho Blue market ID COLLATERAL_TOKEN_CAP=... # Max allocation to collateral token MARKET_CAP=... # Max allocation to specific market # Timelock configuration (in seconds) # Good practice is a minimum 3 days = 259200 seconds VAULT_TIMELOCK_DURATION=259200 ADAPTER_TIMELOCK_DURATION=259200 # Optional: For contract verification ETHERSCAN_API_KEY=... # Your Etherscan API key ``` **Security Note**: Never commit your `.env` file to version control. It contains sensitive information like private keys. ### 3. Simulate the Deployment [#3-simulate-the-deployment] First, simulate the deployment to verify everything works: ```bash source .env && forge script script/DeployVaultV2WithMarketAdapter.s.sol \ --fork-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ -vvv ``` In simulation mode (without `--broadcast`), the script will complete all deployment phases and gracefully skip the dead deposit if the deployer has no tokens. ### 4. Deploy the Morpho Vault V2 [#4-deploy-the-morpho-vault-v2] Run the deployment script with `--broadcast` to execute on-chain: ```bash # Without block explorer verification forge script script/DeployVaultV2WithMarketAdapter.s.sol \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast ``` Or with verification on Etherscan: ```bash # With verification on Etherscan forge script script/DeployVaultV2WithMarketAdapter.s.sol \ --rpc-url $RPC_URL \ --private-key $PRIVATE_KEY \ --broadcast \ --etherscan-api-key $ETHERSCAN_API_KEY \ --verify ``` The script will output the addresses of all deployed contracts and confirm each configuration step. ### Testing Before Mainnet Deployment [#testing-before-mainnet-deployment] The repository includes testing tools to help you validate your deployment: ```bash # Run all tests on fork source .env && forge test --fork-url "$RPC_URL" -vvv # Run specific test forge test --match-test test_FullDeploymentWithTimelocks -vvv ``` **Important**: Always test your deployment configuration on a testnet before deploying to mainnet. ### Good practices [#good-practices] The script addresses the following good practices: | Good practices | How Script Addresses It | | ----------------------- | ------------------------------------------------------------- | | **Non-custodial Gates** | Phase 5: Gates abdicated (cannot be re-enabled) | | **Dead Deposit** | Phase 8: 1e9-1e12 wei deposited on behalf of 0xdead | | **Vault Timelocks** | Phase 9: Configures timelocks for vault functions (>= 3 days) | | **Adapter Timelocks** | Phase 10: Configures adapter timelocks (burnShares >= 3 days) | | **Naming Restriction** | Manual check: Name/symbol cannot contain "morpho" | ### Understanding the Deployment Steps [#understanding-the-deployment-steps] The `DeployVaultV2WithMarketAdapter.s.sol` script orchestrates a comprehensive deployment process. Here's what happens internally: #### Phase 1: Deploy Morpho Vault V2 Instance [#phase-1-deploy-morpho-vault-v2-instance] ```solidity // Deploy Morpho Vault V2 using the factory bytes32 uniqueSalt = keccak256(abi.encodePacked(block.timestamp + gasleft())); VaultV2 deployedVaultV2 = VaultV2( VaultV2Factory(factoryAddress).createVaultV2( temporaryOwner, underlyingAsset, uniqueSalt ) ); ``` #### Phase 2: Configure Temporary Permissions [#phase-2-configure-temporary-permissions] ```solidity // Set broadcaster as temporary curator for configuration vault.setCurator(temporaryCurator); ``` #### Phase 3: Deploy MorphoMarketV1AdapterV2 [#phase-3-deploy-morphomarketv1adapterv2] ```solidity // Deploy MorphoMarketV1AdapterV2 using the adapter factory IMorphoMarketV1AdapterV2Factory factory = IMorphoMarketV1AdapterV2Factory(factoryAddress); address adapterAddress = factory.createMorphoMarketV1AdapterV2(vaultV2Address); ``` #### Phase 4: Submit Timelocked Configuration Changes [#phase-4-submit-timelocked-configuration-changes] ```solidity // Submit all configuration changes that will be timelocked vault.submit(abi.encodeCall(vault.setIsAllocator, (allocator, true))); vault.submit(abi.encodeCall(vault.setAdapterRegistry, (registry))); bytes memory adapterIdData = abi.encode("this", adapter); vault.submit(abi.encodeCall(vault.addAdapter, (adapter))); vault.submit(abi.encodeCall(vault.increaseAbsoluteCap, (adapterIdData, type(uint128).max))); vault.submit(abi.encodeCall(vault.increaseRelativeCap, (adapterIdData, 1e18))); // Submit abdication for registry and gates (non-custodial requirement) vault.submit(abi.encodeCall(vault.abdicate, (IVaultV2.setAdapterRegistry.selector))); vault.submit(abi.encodeCall(vault.abdicate, (IVaultV2.setReceiveSharesGate.selector))); vault.submit(abi.encodeCall(vault.abdicate, (IVaultV2.setSendSharesGate.selector))); vault.submit(abi.encodeCall(vault.abdicate, (IVaultV2.setReceiveAssetsGate.selector))); ``` #### Phase 5: Execute Configuration and Gates Abdication [#phase-5-execute-configuration-and-gates-abdication] ```solidity // Since timelock is 0 initially, execute configurations immediately vault.setAdapterRegistry(registry); vault.setIsAllocator(allocator, true); vault.addAdapter(adapter); bytes memory adapterIdData = abi.encode("this", adapter); vault.increaseAbsoluteCap(adapterIdData, type(uint128).max); vault.increaseRelativeCap(adapterIdData, 1e18); // Abdicate registry and gates (preserves non-custodial properties) vault.abdicate(IVaultV2.setAdapterRegistry.selector); vault.abdicate(IVaultV2.setReceiveSharesGate.selector); vault.abdicate(IVaultV2.setSendSharesGate.selector); vault.abdicate(IVaultV2.setReceiveAssetsGate.selector); ``` #### Phase 6: Set Final Role Assignments [#phase-6-set-final-role-assignments] ```solidity // Assign final roles to production addresses vault.setCurator(vaultCurator); if (vaultSentinel != address(0)) { vault.setIsSentinel(vaultSentinel, true); } vault.setOwner(vaultOwner); ``` #### Phase 7: Configure Market and Liquidity Adapter [#phase-7-configure-market-and-liquidity-adapter] ```solidity // Look up MarketParams from Morpho and set liquidity adapter MarketParams memory marketParams = IMorpho(morpho).idToMarketParams(Id.wrap(marketId)); bytes memory liquidityData = abi.encode(marketParams); vault.setLiquidityAdapterAndData(address(adapter), liquidityData); // Configure collateral token and market caps bytes memory collateralTokenIdData = abi.encode("collateralToken", marketParams.collateralToken); vault.increaseAbsoluteCap(collateralTokenIdData, collateralTokenCap); vault.increaseRelativeCap(collateralTokenIdData, 1e18); bytes memory marketIdData = abi.encode("this/marketParams", address(adapter), marketParams); vault.increaseAbsoluteCap(marketIdData, marketCap); vault.increaseRelativeCap(marketIdData, 1e18); ``` #### Phase 8: Execute Dead Deposit [#phase-8-execute-dead-deposit] ```solidity // Seed vault with initial liquidity to prevent inflation attacks // Amount depends on asset decimals: 1e9 for >= 10 decimals, 1e12 for <= 9 decimals IERC20(vault.asset()).approve(address(vault), depositAmount); vault.deposit(depositAmount, address(0xdead)); // Burned shares ``` #### Phase 9: Configure Vault Timelocks [#phase-9-configure-vault-timelocks] ```solidity // Configure timelocks for vault functions (good practices: >= 3 days) bytes4[] memory selectors = new bytes4[](7); selectors[0] = IVaultV2.addAdapter.selector; selectors[1] = IVaultV2.increaseAbsoluteCap.selector; selectors[2] = IVaultV2.increaseRelativeCap.selector; selectors[3] = IVaultV2.setForceDeallocatePenalty.selector; selectors[4] = IVaultV2.abdicate.selector; selectors[5] = IVaultV2.removeAdapter.selector; selectors[6] = IVaultV2.increaseTimelock.selector; // MUST BE LAST! for (uint256 i = 0; i < selectors.length; i++) { vault.submit(abi.encodeCall(vault.increaseTimelock, (selectors[i], timelockDuration))); vault.increaseTimelock(selectors[i], timelockDuration); } ``` #### Phase 10: Configure Adapter Timelocks [#phase-10-configure-adapter-timelocks] ```solidity // Configure timelocks for adapter functions (good practices: burnShares >= 3 days) bytes4[] memory selectors = new bytes4[](4); selectors[0] = IMorphoMarketV1AdapterV2.abdicate.selector; selectors[1] = IMorphoMarketV1AdapterV2.setSkimRecipient.selector; selectors[2] = IMorphoMarketV1AdapterV2.burnShares.selector; selectors[3] = IMorphoMarketV1AdapterV2.increaseTimelock.selector; // MUST BE LAST! for (uint256 i = 0; i < selectors.length; i++) { adapter.submit(abi.encodeCall(adapter.increaseTimelock, (selectors[i], timelockDuration))); adapter.increaseTimelock(selectors[i], timelockDuration); } ``` ## Method 3: Create a Vault Manually via Etherscan [#method-3-create-a-vault-manually-via-etherscan] If you prefer a manual approach without using the Curator App or deployment scripts, you can deploy a vault directly by interacting with the contracts on a block explorer like Etherscan. This method requires deploying the vault directly via the `VaultV2` constructor. You will need to perform all the role assignment and configuration steps separately, as detailed in the [Configure Vault V2 Roles guide](/curate/tutorials-v2/roles/). This approach is more complex and error-prone than using the Curator App or deployment scripts. ### Prerequisites [#prerequisites-1] * An Ethereum wallet (e.g., MetaMask) connected to the desired network. * The address of the ERC-20 token the vault will use as its `asset`. * The address you want to designate as the initial `owner`. ### Step-by-Step Guide [#step-by-step-guide] ### 1. Deploy a New VaultV2 Contract [#1-deploy-a-new-vaultv2-contract] Since Vault V2 doesn't use a factory pattern, you'll need to deploy the contract directly. This typically requires using a development environment like Remix or Foundry. For Remix: 1. Copy the `VaultV2.sol` contract and its dependencies. 2. Compile with Solidity 0.8.28. 3. Deploy with constructor parameters: * `_owner`: The initial owner address (ideally your multisig). * `_asset`: The ERC-20 token address (e.g., USDC, WETH). ### 2. Verify the Contract [#2-verify-the-contract] After deployment, verify the contract on Etherscan to enable the `Write Contract` interface. ### 3. Configure the Vault [#3-configure-the-vault] Once deployed and verified, you'll need to: 1. Set up roles (see [Configure Vault V2 Roles](/curate/tutorials-v2/roles/)). 2. Deploy and configure adapters (see [(De)List Adapters](/curate/tutorials-v2/listing-adapters/)). **Congratulations!** Your vault is now deployed. For a production-ready setup, we strongly recommend using the deployment script method as it ensures all components are properly configured. --- # Migrate from MorphoVaultV1Adapter to MorphoMarketV1AdapterV2 Source: https://docs.morpho.org/curate/tutorials-v2/vault-v1-adapter-migration ## Overview [#overview] This guide describes the standard deprecation flow for replacing a Vault V2's `MorphoVaultV1Adapter` with a `MorphoMarketV1AdapterV2`, giving the vault direct access to Morpho Variable Rate Markets instead of routing through a Morpho Vault V1. This is a **normal operational migration**, not an emergency. All contracts are functioning correctly and assets can be moved gracefully. For emergency procedures involving malfunctioning adapters, see [Emergency Procedures (Vaults V2)](/curate/tutorials-v2/emergency/). The migration combines two standard curation workflows: 1. **List** the new `MorphoMarketV1AdapterV2` and its target markets, see [(De)List Adapters](/curate/tutorials-v2/listing-adapters/) and [(De)List Markets](/curate/tutorials-v2/market-listing/). 2. **Delist** the old `MorphoVaultV1Adapter`. ## Migration Flow [#migration-flow] ### 1. Deploy and Add the MorphoMarketV1AdapterV2 [#1-deploy-and-add-the-morphomarketv1adapterv2] If the Vault V2 does not already have a `MorphoMarketV1AdapterV2`, deploy one through the `MorphoMarketV1AdapterV2Factory`: ```solidity factory.createMorphoMarketV1AdapterV2(vaultV2Address) ``` Then enable it on the vault. This is a **timelocked** action: 1. As **Curator**, call `submit(abi.encodeCall(IVaultV2.addAdapter, (adapterAddress)))`. 2. After the timelock expires, call `addAdapter(adapterAddress)`. See [(De)List Adapters: via Etherscan](/curate/tutorials-v2/listing-adapters/) for the full walkthrough, or use the **Curator App V2** for a guided flow. **Outcome:** The Vault V2 now has a live `MorphoMarketV1AdapterV2` available for allocations. ### 2. Set Caps for the New Adapter and Target Markets [#2-set-caps-for-the-new-adapter-and-target-markets] Before allocating assets, set the caps required by the new setup. Morpho Vault V2 enforces a **three-layer cap hierarchy**. All three must be non-zero for the vault to allocate to a given market: 1. **Adapter cap**: ceiling for the whole `MorphoMarketV1AdapterV2`. 2. **Collateral token cap**: ceiling across all markets sharing the same collateral asset. 3. **Market cap**: ceiling for each specific `MarketParams` tuple. Cap increases are **timelocked**: submit first via `submit(bytes)`, then execute after the timelock elapses. All cap proposals can be batched and accepted together once the timelock passes. #### Via the Curator App V2 (recommended) [#via-the-curator-app-v2-recommended] On the **Caps** tab: * Click **"Edit Caps"** to set the adapter absolute and relative caps. * Click **"Add collateral"** to set collateral token caps for each collateral used by the target markets. * Click **"Add market"** to set market caps for each target market. * Use the **"Timelocks"** button to batch-accept all caps once the timelock has passed. #### Via Etherscan [#via-etherscan] Submit and execute cap increases for each layer: ```solidity // Adapter cap bytes memory adapterIdData = abi.encode("this", adapterAddress); vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (adapterIdData, capAmount))); vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (adapterIdData, relativeCapWad))); // Collateral token cap (for each collateral) bytes memory collateralIdData = abi.encode("collateralToken", collateralTokenAddress); vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (collateralIdData, capAmount))); vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (collateralIdData, relativeCapWad))); // Market cap (for each market) bytes memory marketIdData = abi.encode("this/marketParams", adapterAddress, marketParams); vault.submit(abi.encodeCall(IVaultV2.increaseAbsoluteCap, (marketIdData, capAmount))); vault.submit(abi.encodeCall(IVaultV2.increaseRelativeCap, (marketIdData, relativeCapWad))); ``` After the timelock, execute each function directly, for example: ```solidity vault.increaseAbsoluteCap(idData, capAmount); ``` See [(De)List Markets](/curate/tutorials-v2/market-listing/) for the full cap-setting walkthrough. **Outcome:** The Vault V2 is allowed to allocate into the selected markets through the new adapter. ### 3. Allocate into the Target Markets [#3-allocate-into-the-target-markets] Once caps are accepted, the **Allocator** directs the vault's idle assets into the target Morpho Variable Rate Markets through the new adapter: ```solidity bytes memory data = abi.encode(marketParams); vault.allocate(marketAdapterAddress, data, amount); ``` If one of these markets is intended to become the vault's liquidity market (see Step 5), seed it with enough assets before switching the liquidity adapter. **Outcome:** The new `MorphoMarketV1AdapterV2` holds live positions for the Vault V2. ### 4. Deallocate from the Old MorphoVaultV1Adapter [#4-deallocate-from-the-old-morphovaultv1adapter] The **Allocator** (or **Sentinel**) calls `deallocate` to withdraw assets from the old `MorphoVaultV1Adapter` back to the vault's idle pool: ```solidity vault.deallocate(oldVaultV1Adapter, bytes(""), assets); ``` The `assets` parameter requires an exact amount, neither the vault nor the adapter interprets `type(uint256).max` as "withdraw all." Query the current allocation and pass the precise value. If full withdrawal is not immediately possible because the underlying Vault V1 is illiquid, deallocate progressively as liquidity becomes available. Set up monitoring to deallocate as assets become available. In parallel, continue allocating into the target markets under the new `MorphoMarketV1AdapterV2`. **Outcome:** Assets are migrated from the old adapter into the new market adapter setup. ### 5. Switch the Liquidity Adapter [#5-switch-the-liquidity-adapter] If the old `MorphoVaultV1Adapter` is currently set as the vault's `liquidityAdapter`, switch to the new `MorphoMarketV1AdapterV2` **only after** the intended liquidity market is funded enough to handle deposits and withdrawals. As **Allocator**, call: ```solidity bytes memory liquidityData = abi.encode(marketParams); vault.setLiquidityAdapterAndData(marketAdapterAddress, liquidityData); ``` Where `marketParams` identifies the specific Morpho Variable Rate Market that will receive deposits and serve withdrawals. See [Liquidity Curation with MarketV1AdapterV2](/curate/tutorials-v2/liquidity-curation/) for details on how the liquidity adapter works and best practices for dynamic reallocation. If the old adapter is the current `liquidityAdapter`, it **must** be unset before removal. Otherwise, deposits into the vault will revert. **Outcome:** Deposits and withdrawals are routed through the new market adapter. ### 6. Delist the Old MorphoVaultV1Adapter [#6-delist-the-old-morphovaultv1adapter] Once the old adapter is empty and no longer set as the liquidity adapter, follow the standard adapter soft deprecation flow. #### Step 1: Set Caps to Zero [#step-1-set-caps-to-zero] The **Curator** or **Sentinel** decreases the adapter's caps to zero. This is **instant** and the cap decreases are exempt from timelocks: ```solidity bytes memory adapterIdData = abi.encode("this", oldVaultV1Adapter); vault.decreaseAbsoluteCap(adapterIdData, 0); vault.decreaseRelativeCap(adapterIdData, 0); ``` Setting caps to zero before removal is **critical**. Without this step, an allocator could frontrun the timelocked `removeAdapter` call by allocating assets into the adapter during the timelock window. Since `removeAdapter` does **not** revert if the adapter still holds assets, those assets would be permanently lost. See [Security Considerations](/curate/concepts/security-considerations/#adapter-removal-frontrunning). #### Step 2: Remove the Adapter [#step-2-remove-the-adapter] Once the adapter's allocation is zero and its caps are zero, the **Curator** can submit `removeAdapter` to delist it from the vault. This is a **timelocked** action: ```solidity vault.submit(abi.encodeCall(IVaultV2.removeAdapter, (oldVaultV1Adapter))); // After timelock expires vault.removeAdapter(oldVaultV1Adapter); ``` **Outcome:** The migration is complete and the old `MorphoVaultV1Adapter` is deprecated. ## Checklist [#checklist] * [ ] Identify target Variable Rate Markets and their collateral tokens. * [ ] Deploy `MorphoMarketV1AdapterV2` from factory, if needed. * [ ] Add the adapter and set adapter caps, collateral caps, and market caps (timelocked). * [ ] Allocate into target markets and seed the liquidity market if switching the liquidity adapter. * [ ] Deallocate from the old `MorphoVaultV1Adapter`, progressively if needed. * [ ] Switch the liquidity adapter to `MorphoMarketV1AdapterV2` and verify the old adapter allocation is zero. * [ ] Zero out old `MorphoVaultV1Adapter` caps and remove the old `MorphoVaultV1Adapter`. ## Operational Notes [#operational-notes] * Cap increases and adapter additions are timelocked. * Cap decreases are instant and can be executed by the Sentinel. * Deallocation from the old `MorphoVaultV1Adapter` may require multiple transactions if the underlying Vault V1 is illiquid. * The old adapter should only be removed after its allocation is zero, the liquidity adapter has been switched if relevant, and the new market adapter is fully operational. * See [Liquidity Curation](/curate/tutorials-v2/liquidity-curation/) for best practices on running a reallocation bot and configuring `forceDeallocatePenalty`. --- # Get Started Connect Claude Code, Codex, or Cursor to Morpho — query vaults, markets, and positions, and prepare unsigned transactions from your agent. Source: https://docs.morpho.org/developers/agents/get-started Morpho Agents gives an AI agent structured access to the Morpho protocol. Ask it for a vault's realized average APY, a market's borrow rate, or a wallet's health factor, and it answers from live onchain and API data. Ask it to deposit, borrow, or repay, and it hands back an unsigned transaction for you to review and sign. Morpho Agents is **experimental** and pre-v1.0. Tool schemas, command syntax, and behavior may change without notice. Do not build production systems against it yet. ## Quickstart [#quickstart] The shortest path is the hosted MCP server. It needs no account, no API key, and nothing to install — point your agent at `https://mcp.morpho.org/` and it can read Morpho. ```bash claude mcp add morpho --scope user --transport http https://mcp.morpho.org/ ``` ```bash codex mcp add morpho --url https://mcp.morpho.org/ ``` Add the server to `.cursor/mcp.json`: ```json { "mcpServers": { "morpho": { "type": "http", "url": "https://mcp.morpho.org/" } } } ``` Then ask your agent a question: ```text For vault 0x… on Base, what are its realized average APY, TVL, curator, and fee rates? ``` For per-client setup, the full tool reference, and more prompts, see [Morpho MCP](/developers/agents/mcp/). ## Two ways to connect [#two-ways-to-connect] Morpho MCP and Morpho Skills serve different tasks. Use the hosted server to query the protocol or prepare unsigned transactions in a conversation. Install the skills when an agent is building, updating, or reviewing product code. | | [Morpho MCP](/developers/agents/mcp/) | [Morpho Skills](/developers/agents/skills/) | | --------------------- | -------------------------------------- | ------------------------------------------------------- | | How the agent uses it | Tool calls to a hosted server | Local product guidance, references, and review checkers | | Setup | Connect to `https://mcp.morpho.org/` | `npx skills add morpho-org/morpho-skills` | | Best for | Live queries and unsigned transactions | Building and reviewing Earn and Borrow products | The `earn-integration` and `borrow-integration` skills each include build/update and review modes. They cover current data sources, SDK transaction flows, product UI, safety checks, and evidence-based pre-launch reviews. ## What your agent can do [#what-your-agent-can-do] **Read** — list and inspect vaults and markets, fetch a user's positions and token balances, and check which chains are supported. **Prepare** — build unsigned transactions for vault deposits and withdrawals, market supply, borrow, and repay, collateral supply and withdrawal, and rewards claims. Prepare operations return **unsigned** transaction payloads. Nothing signs and nothing broadcasts — you always review and submit the transaction yourself. Treat every payload as untrusted until you have read it. Morpho Agents works across every chain Morpho is deployed on. Rather than hardcoding a list that will drift, ask your agent to call `morpho_get_supported_chains` — it returns each chain's slug, chain ID, and explorer URL. ## Next steps [#next-steps] * **[Morpho MCP](/developers/agents/mcp/)** — endpoint details, per-client setup, the full tool table, and prompt examples. * **[Morpho Skills](/developers/agents/skills/)** — install the Earn and Borrow builder skills, and see when to use each mode. * **[Using LLMs](/developers/agents/llms/)** — feed these docs to an agent as plain Markdown. --- # llms.txt Source: https://docs.morpho.org/developers/agents/llms Morpho's documentation is available in LLM-friendly formats to help developers and AI agents integrate with Morpho faster. ## Available Endpoints [#available-endpoints] | Endpoint | Description | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | [llms.txt](https://docs.morpho.org/llms.txt) | Index of every docs page with one-line descriptions. Start here. | | [llms-full.txt](https://docs.morpho.org/llms-full.txt) | Every page concatenated into one markdown document, with code snippets inlined. Best for full context. | | `.md` | Any single page as raw markdown — append `.md` to its URL, e.g. [get-started.md](https://docs.morpho.org/get-started.md). | **Which one should I use?** * **Quick lookup** — read `llms.txt` to find the right page, then fetch just that page as markdown by appending `.md` to its URL. * **Full context** — use `llms-full.txt` for the complete documentation in a single request. --- # Morpho MCP Morpho's hosted MCP server — query vaults, markets, and positions, and prepare unsigned transactions from any MCP-compatible agent. Source: https://docs.morpho.org/developers/agents/mcp Morpho MCP is Morpho's hosted, remote [MCP](https://modelcontextprotocol.io) server. It gives any MCP-compatible agent live access to Morpho vaults, markets, and positions, and lets it prepare unsigned transactions for you to review and sign. Morpho MCP is **experimental** and pre-v1.0. Tool names, schemas, and behavior may change without notice. Do not build production systems against it yet. ## Setup and Usage [#setup-and-usage] The server is hosted, public, and read-only by design. There is nothing to install and no key to manage. | | | | ------------------ | ----------------------------- | | **Endpoint** | `https://mcp.morpho.org/` | | **Transport** | Streamable HTTP | | **Authentication** | None — no API key, no account | | **Server name** | `morpho-mcp` | Two conventions apply to protocol calls: * **Query tools accept chain IDs.** Pass exactly one of `chainId` or `chain` to `morpho_query_vaults` and `morpho_query_markets`. Prefer the numeric `chainId` from `morpho_get_supported_chains`. The legacy `chain` slug remains supported. * **RPC tools require registered slugs.** The get, position, and prepare tools still require `chain`. They accept only entries where `morpho_get_supported_chains` returns `supportsRpcTools: true`. REST-only chain IDs work only with the two query tools. * **Discover before you write.** Find a target with `morpho_query_vaults` or `morpho_query_markets`, inspect it with `morpho_get_vault` or `morpho_get_market`, then call a `morpho_prepare_*` tool. Never hardcode addresses. `morpho_get_supported_chains` also returns `supportedProtocols` when Core REST provides it. Registered chains include a slug, explorer URL, and testnet status. REST-only entries can omit those fields. A `morpho_prepare_*` tool returns a `PreparedOperation`: the `transactions` to sign, any `requirements` such as ERC-20 approvals (already included in `transactions`), an `outcome` preview of the resulting position, and a `warnings` array. Warnings carry a severity — an `error` means stop, and the transactions must not be presented for signing. Prepare tools return **unsigned** transaction payloads. The server never signs and never broadcasts. You always review and submit the transaction yourself — treat every payload as untrusted until you have read it. ## Setup Examples [#setup-examples] ### Claude Code [#claude-code] Add the server with the `claude mcp add` command. The `--scope user` flag registers it once for every project on your machine; drop it to scope the server to the current project. ```bash claude mcp add morpho --scope user --transport http https://mcp.morpho.org/ ``` Confirm it connected: ```bash claude mcp list ``` Inside a session, `/mcp` lists the connected servers and the tools each exposes. ### Codex [#codex] Add the server with `codex mcp add`, passing `--url` to select the Streamable HTTP transport: ```bash codex mcp add morpho --url https://mcp.morpho.org/ ``` Confirm it connected: ```bash codex mcp list ``` You can also declare the server in `~/.codex/config.toml` to make it global, or in a trusted project's `.codex/config.toml` to scope it to that project: ```toml [mcp_servers.morpho] url = "https://mcp.morpho.org/" ``` A `morpho_prepare_*` call simulates the transaction before returning, so it can take longer than a plain read. If Codex times out with a transport error, raise the per-server tool timeout: ```toml [mcp_servers.morpho] url = "https://mcp.morpho.org/" tool_timeout_sec = 120 ``` ### Other clients [#other-clients] Any MCP client that speaks the Streamable HTTP transport can connect by pointing at the endpoint. In Cursor, add it to `.cursor/mcp.json`: ```json { "mcpServers": { "morpho": { "type": "http", "url": "https://mcp.morpho.org/" } } } ``` ## Tools [#tools] The server exposes eighteen tools. Read tools answer questions about protocol state or the documentation; prepare tools return unsigned transactions. | Category | Tool | Description | | ---------------- | ------------------------------------ | -------------------------------------------------------------------------------- | | **Utility** | `morpho_health_check` | Check the server and its connected services. | | **Utility** | `morpho_get_supported_chains` | List known chains, Core protocols, and RPC-tool support. | | **Vaults** | `morpho_query_vaults` | List and sort one vault version, with asset filtering and cursor pagination. | | **Vaults** | `morpho_get_vault` | Get one vault's details. | | **Markets** | `morpho_query_markets` | List markets with REST-supported asset and listed filters and cursor pagination. | | **Markets** | `morpho_get_market` | Get one market's details. | | **Positions** | `morpho_get_market_positions` | Get positions for a user across 1–100 explicit market IDs. | | **Positions** | `morpho_get_vault_positions` | List a user's V1 or V2 vault positions with cursor pagination. | | **Positions** | `morpho_get_token_balance` | A token balance, plus whether Morpho or the Bundler still needs an approval. | | **Docs** | `morpho_doc_search` | Search the Morpho documentation by semantic similarity. | | **Transactions** | `morpho_prepare_deposit` | Deposit into a vault, including any approval. | | **Transactions** | `morpho_prepare_withdraw` | Withdraw from a vault. Accepts `"max"`. | | **Transactions** | `morpho_prepare_supply` | Supply to a market, including any approval. | | **Transactions** | `morpho_prepare_borrow` | Borrow from a market, with a health-factor check. | | **Transactions** | `morpho_prepare_repay` | Repay a market borrow. Accepts `"max"`. | | **Transactions** | `morpho_prepare_supply_collateral` | Supply collateral without borrowing, including any approval. | | **Transactions** | `morpho_prepare_withdraw_collateral` | Withdraw collateral. Accepts `"max"`. | | **Transactions** | `morpho_prepare_claim_rewards` | Claim Merkl rewards against the canonical Distributor. | For product implementation and review guidance, install the separate [Morpho Skills](/developers/agents/skills/). Vault `apyPct` is a realized average APY. Market `supplyApyPct` and `borrowApyPct` are 24-hour averages. All three are percentage strings from the Core REST API and exclude rewards. V2 vault fees are separate: `performanceFeePct` is charged against distributed interest, while `managementFeeAprPct` annualizes the per-second fee charged against total assets. Both query responses always include `chainId` as a string. They also include `chain` when the ID has a registered MCP slug. Vault queries can sort by `address`, `name`, `creationTimestamp`, `creationBlockNumber`, or `performanceFee`. V2 queries can also sort by `managementFee`. Pass `sortBy` and `sortDirection` (`asc` or `desc`) together, or omit both. ## Prompt Examples [#prompt-examples] Once the server is connected, ask in plain language. Your agent picks the tools. 1. For vault `0x…` on Base, show its realized average APY, TVL, curator, and fee rates. Note that the APY excludes rewards. 2. Compare the borrow rates on the wstETH/WETH markets on Ethereum and Arbitrum. 3. For `0x…` on Base, show positions in market IDs `0x…` and `0x…`, and flag any health factor below 1.2. 4. List the V2 vault positions for `0x…` on Base, following every cursor page. 5. Prepare a 1,000 USDC deposit into vault `0x…` on Base for `0x…`, and show me the approval and deposit transactions before I sign anything. 6. If I borrow 1 WETH against my wstETH collateral on Ethereum, what is my health factor afterwards? 7. Do I have claimable Merkl rewards on Base? If so, prepare the claim. 8. Which chains does Morpho support, and what are their chain IDs? --- # Skills Install Morpho's builder skills to build, update, and review Earn and Borrow products. Source: https://docs.morpho.org/developers/agents/skills The `morpho-builder` plugin gives coding agents two self-contained skills for Morpho Earn and Borrow products. Each skill covers current data sources, `@morpho-org/morpho-sdk` transaction flows, product UI, safety guidance, and pre-launch review. ## Install [#install] Install both skills on an [Agent Skills](https://agentskills.io)-compatible host: ```bash npx skills add morpho-org/morpho-skills ``` Or pick individual skills: ```bash npx skills add morpho-org/morpho-skills --skill earn-integration npx skills add morpho-org/morpho-skills --skill borrow-integration ``` Claude Code can install the same skills from the Morpho marketplace: ```bash /plugin marketplace add morpho-org/morpho-skills /plugin install morpho-builder@morpho-skills ``` Both installation paths expose the same skills, product references, review workflow, and checker prompts. ## Available skills [#available-skills] | Plugin | Skill | Product | | ---------------- | -------------------- | -------------------------------------------- | | `morpho-builder` | `earn-integration` | Earn with Morpho Vault V2 | | `morpho-builder` | `borrow-integration` | Borrow with Blue, Midnight, or both products | Pick the skill by product. The same skill handles build, update, review, audit, quality assurance, and pre-launch requests. There are no separate review skills. ## Integration skills [#integration-skills] ### Build or update [#build-or-update] * **Earn** defaults to Morpho Vault V2. It covers vault discovery, live data, variable APY and rewards, deposit and withdrawal actions, liquid and illiquid exits, positions, UI copy, and safety checks. Vault V1 appears only as migration or compatibility context. * **Borrow** first identifies Blue variable-rate borrowing, Midnight fixed-rate fixed-term borrowing, or both. It then loads product-specific data, transaction, rate, risk, liquidity, refinancing, quote, maturity, and UI guidance. A mixed integration keeps Blue and Midnight behavior separate. Both skills use the Morpho API for indexed discovery and fresh protocol entities for transaction execution. They guide the agent through SDK requirements, transaction building, final simulation, submission, and receipt reconciliation. ### Review or audit [#review-or-audit] Review mode runs eight evidence-based checkers: vocabulary, attribution, disclosure, rate transparency, conversion, clarity and safety, discoverability, and math correctness. The skill runs them in parallel when the agent can delegate, or sequentially otherwise. The skill validates checker results against a product acceptance matrix, performs a red-flag pass, and returns a release recommendation. Mixed Borrow reviews keep separate Blue and Midnight verdicts. A review reports missing evidence as `UNVERIFIED` and does not edit the product unless you also request fixes. ## Use cases [#use-cases] **Build or update an Earn product.** Ask the agent to use `earn-integration` for a Vault V2 discovery, deposit, position, or exit flow. **Build or update a Borrow product.** Ask the agent to use `borrow-integration` and name Blue, Midnight, or both when you know the target product. **Run a pre-launch review.** Ask the same product skill to review the integration. It returns evidence-backed checker results, the acceptance matrix, red flags, and a release recommendation. --- # Morpho API Changelog Source: https://docs.morpho.org/developers/api/changelog This page tracks Morpho API field deprecations, field removals, and other API changes. The Morpho API exposes deprecation warnings directly in GraphQL responses, letting integrators detect deprecated fields at query time before they are removed. See [Deprecation warnings](/developers/api/get-started/#deprecation-warnings) for details on how to use them. In this changelog:
| Change type | Meaning | | --------------------- | --------------------------------------------------------------------------------------------------------- | | **Field deprecation** | A field that is still present in the schema but not maintained anymore. Will be removed at a later stage. | | **Field removal** | A field that is or will be removed from the schema. Using this field in a query will throw an error. |
## Deprecation/Removal Rollout - May 2026 to October 2026 [#deprecationremoval-rollout---may-2026-to-october-2026]
October 21, 2026 - View scheduled field removals The below fields will be fully removed from the Morpho API on **October 21, 2026**
| **Deprecated Field** | **Replacement** | **Deprecation date** | | ---------------------------------- | ---------------- | -------------------- | | `Asset.priceUsd` | `price.usd` | 2026-03-19 | | `Market.oracleAddress` | `oracle.address` | 2025-10-14 | | `Market.targetBorrowUtilization` | N/A | 2026-04-13 | | `Market.targetWithdrawUtilization` | N/A | 2026-04-13 | | `VaultMetadata.forumLink` | N/A | 2026-04-10 | | `VaultV2Metadata.forumLink` | N/A | 2026-04-10 |
September 30, 2026 - View scheduled field removals The below fields will be fully removed from the Morpho API on **September 30, 2026**
| **Deprecated Field** | **Replacement** | **Deprecation date** | | --------------------------------- | --------------------------- | -------------------- | | `Asset.oraclePriceUsd` | `price.usd` | 2026-02-05 | | `Asset.spotPriceEth` | N/A | 2026-01-20 | | `Asset.totalSupply` | N/A | 2026-01-20 | | `Market.monthlyApys` | `state.monthlyApys` | 2025-09-09 | | `Market.oracleInfo` | `oracle.data` | 2025-10-14 | | `VaultState.dailyApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.dailyNetApy` | `avgNetApy` | 2026-02-15 | | `VaultState.netApyWithoutRewards` | `netApyExcludingRewards` | 2026-02-15 | | `VaultState.rewards` | `allRewards` | 2026-03-30 | | `VaultState.weeklyApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.weeklyNetApy` | `avgNetApy` | 2026-02-15 |
August 26, 2026 - View scheduled field removals The below fields will be fully removed from the Morpho API on **August 26, 2026**
| **Deprecated Field** | **Replacement** | **Deprecation date** | | ----------------------------------------- | --------------------------- | -------------------- | | `Market.dailyApys` | `state.dailyApys` | 2025-09-09 | | `Query.oracleFeedByAddress` | N/A | 2026-07-10 | | `Query.oracleFeeds` | N/A | 2026-07-10 | | `Query.oracleVaultByAddress` | N/A | 2026-07-10 | | `Query.oracleVaults` | N/A | 2026-07-10 | | `Query.publicAllocator` | N/A | 2026-07-10 | | `Query.publicAllocators` | N/A | 2026-07-10 | | `Query.vaultFactories` | N/A | 2026-07-10 | | `Query.vaultFactoryByAddress` | N/A | 2026-07-10 | | `Query.vaultV2Factories` | N/A | 2026-07-10 | | `Query.vaultV2MetaMorphoAdapterFactories` | N/A | 2026-07-10 | | `VaultV2.avgApy` | `avgNetApyExcludingRewards` | 2026-02-15 |
July 29, 2026 - View scheduled field removals The below fields will be fully removed from the Morpho API on **July 29, 2026**
| **Deprecated Field** | **Replacement** | **Deprecation date** | | ---------------------------- | ------------------------------------------------------------------ | -------------------- | | `Asset.isWhitelisted` | `isListed` | 2025-12-22 | | `Market.collateralPrice` | `state.price` | 2025-09-09 | | `Market.creatorAddress` | `morphoBlue` | 2026-01-27 | | `Query.transactions` | `vaultV2transactions`, `vaultV1Transactions`, `marketTransactions` | 2026-04-23 | | `User.transactions` | `vaultV1Transactions`, `marketTransactions` | 2026-04-23 | | `VaultState.lastTotalAssets` | `totalAssets` | 2026-04-16 |
June 24, 2026 - View scheduled field removals The below fields will be fully removed from the Morpho API on **June 24, 2026**
| **Deprecated Field** | **Replacement** | **Deprecation date** | | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------- | | `Asset.historicalSpotPriceEth` | N/A | 2026-01-20 | | `Asset.riskAnalysis` | N/A | 2026-01-20 | | `AssetsFilters.credoraRiskScore_gte` | N/A | 2025-11-26 | | `AssetsFilters.credoraRiskScore_lte` | N/A | 2025-11-26 | | `Curator.url` | `socials.url` | 2025-09-09 | | `Market.concentration` | N/A | 2025-09-09 | | `Market.id` | `marketId` and `chain.id` | 2026-05-14 | | `Market.oracleFeed` | `oracle.data` | 2025-10-14 | | `Market.riskAnalysis` | N/A | 2025-09-09 | | `MarketConcentration.borrowHhi` | N/A | 2026-01-28 | | `MarketConcentration.supplyHhi` | N/A | 2026-01-28 | | `MarketFilters.credoraRiskScore_gte` | N/A | 2025-11-26 | | `MarketFilters.credoraRiskScore_lte` | N/A | 2025-11-26 | | `MarketHistory.fee` | N/A | 2026-01-07 | | `MarketHistory.price` | N/A | 2026-01-19 | | `MarketOracleAccuracy.`
`averagePercentDifference` | N/A | 2025-09-09 | | `MarketOracleAccuracy.`
`maxPercentDifference` | N/A | 2025-09-09 | | `MarketStateReward.`
`amountPerBorrowedToken` | N/A | 2025-09-12 | | `MarketStateReward.`
`amountPerSuppliedToken` | N/A | 2025-09-12 | | `MarketStateReward.yearlyBorrowTokens` | N/A | 2025-09-12 | | `MarketStateReward.yearlySupplyTokens` | N/A | 2025-09-12 | | `OracleFeed.description` | N/A | 2025-10-08 | | `OracleFeed.historicalPrice` | N/A | 2025-10-08 | | `OracleFeed.pair` | N/A | 2025-10-08 | | `OracleFeed.price` | N/A | 2025-10-08 | | `OracleFeed.vendor` | N/A | 2025-10-08 | | `OracleVault.decimals` | N/A | 2025-10-20 | | `OracleVault.pair` | N/A | 2025-10-20 | | `OracleVault.vendor` | N/A | 2025-10-20 | | `Query.marketOracleAccuracy` | `marketById(marketId:, chainId:)` | 2026-05-14 | | `Query.transactionByHash` | N/A | 2025-01-02 | | `Query.users` | `userByAddress(address:, chainId:)` when the address is known; otherwise use address-scoped queries | 2026-05-14 | | `User.id` | `address` and `chain.id` | 2026-05-14 | | `User.tag` | N/A | 2026-05-14 | | `Vault.id` | `address` and `chain.id` | 2026-05-14 | | `VaultAllocationDto.enabled` | N/A | 2026-01-15 | | `VaultFilters.credoraRiskScore_gte` | N/A | 2025-11-26 | | `VaultFilters.credoraRiskScore_lte` | N/A | 2025-11-26 | | `VaultState.id` | Parent `Vault.address` and `Vault.chain.id` | 2026-05-14 | | `VaultStateReward.`
`amountPerSuppliedToken` | N/A | 2026-03-20 | | `VaultStateReward.`
`yearlySupplyTokens` | N/A | 2026-03-20 |
When fetching an entity, pass `chainId` as an input argument where available. When reading an entity from the response, select `chain { id }`.
May 20, 2026 - View scheduled field removals The below fields will be fully removed from the Morpho API on **May 20, 2026**
| **Deprecated Field** | **Replacement** | **Deprecation date** | | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------- | | `AssetsFilters.id_in` | `address_in` | 2026-02-11 | | `AssetsFilters.whitelisted` | `listed` | 2025-12-22 | | `CuratorFilters.chainId` | `chainId_in` | 2025-09-09 | | `Market.allTimeApys` | `state.allTimeApy` | 2025-09-09 | | `Market.quarterlyApys` | `state.quarterlyApys` | 2025-09-09 | | `Market.uniqueKey` | `marketId` | 2026-03-30 | | `Market.weeklyApys` | `state.weeklyApys` | 2025-09-09 | | `Market.whitelisted` | `listed` | 2026-01-29 | | `Market.yearlyApys` | `state.yearlyApys` | 2025-09-09 | | `MarketFilters.collateralAssetId_in` | `collateralAssetSelector_in` | 2026-01-29 | | `MarketFilters.id_in` | `selector_in` | 2026-01-29 | | `MarketFilters.loanAssetId_in` | `loanAssetSelector_in` | 2026-01-29 | | `MarketFilters.whitelisted` | `listed` | 2026-02-27 | | `MarketHistory.allTimeNetBorrowApy` | `allTimeBorrowApy` | 2026-01-19 | | `MarketHistory.allTimeNetSupplyApy` | `allTimeSupplyApy` | 2026-01-19 | | `MarketHistory.rateAtUTarget` | `apyAtTarget` | 2025-12-23 | | `MarketPosition.borrowAssets` | `state.borrowAssets` | 2025-09-09 | | `MarketPosition.borrowAssetsUsd` | `state.borrowAssetsUsd` | 2025-09-09 | | `MarketPosition.borrowShares` | `state.borrowShares` | 2025-09-09 | | `MarketPosition.collateral` | `state.collateral` | 2025-09-09 | | `MarketPosition.collateralUsd` | `state.collateralUsd` | 2025-09-09 | | `MarketPosition.supplyAssets` | `state.supplyAssets` | 2025-09-09 | | `MarketPosition.supplyAssetsUsd` | `state.supplyAssetsUsd` | 2025-09-09 | | `MarketPosition.supplyShares` | `state.supplyShares` | 2025-09-09 | | `MarketPosition.whitelisted` | `listed` | 2026-02-03 | | `MarketPositionFilters.marketId_in` | `marketUniqueKey_in` | 2026-02-11 | | `MarketPositionFilters.`
`marketWhitelisted` | `marketListed` | 2026-02-27 | | `MarketPositionFilters.userId_in` | `userAddress_in` | 2026-01-20 | | `MarketState.rateAtUTarget` | `apyAtTarget` | 2026-01-07 | | `MarketStateReward.borrowApy` | `borrowApr` | 2024-05-28 | | `MarketStateReward.supplyApy` | `supplyApr` | 2024-05-28 | | `MorphoBlueFilters.id_in` | `address_in` | 2026-02-11 | | `MorphoChainlinkOracleData.vault` | `baseOracleVault` | 2025-09-09 | | `MorphoChainlinkOracleV2Data.baseVault` | `baseOracleVault` | 2025-09-09 | | `MorphoChainlinkOracleV2Data.quoteVault` | `quoteOracleVault` | 2025-09-09 | | `PublicAllocatorFilters.id_in` | `address_in` | 2026-02-11 | | `PublicAllocatorSharedLiquidity.`
`allocationMarket` | `withdrawMarket` | 2025-12-17 | | `PublicAllocatorSharedLiquidity.market` | `supplyMarket` | 2025-12-17 | | `PublicallocatorReallocateFilters.`
`marketId_in` | `marketSelector_in` | 2026-01-27 | | `PublicallocatorReallocateFilters.`
`vaultId_in` | `vaultSelector_in` | 2026-01-27 | | `Query.asset` | `assetByAddress` | 2026-02-12 | | `Query.market` | `marketById` | 2025-09-09 | | `Query.marketAverageApys` | `state` | 2025-07-28 | | `Query.marketByUniqueKey` | `marketById` | 2026-04-01 | | `Query.morphoBlue` | `morphoBlueByAddress` | 2026-02-12 | | `Query.search` | `markets and vaults` | 2026-03-20 | | `Query.transaction` | `transactions` | 2026-02-12 | | `Query.user` | `userByAddress` | 2026-02-12 | | `Query.vault` | `vaultByAddress` | 2026-02-12 | | `Query.vaultFactory` | `vaultFactoryByAddress` | 2026-02-12 | | `RiskAnalysis.isUnderReview` | `analysis.isUnderReview` | 2025-09-09 | | `RiskAnalysis.rating` | `analysis.rating` | 2025-09-09 | | `RiskAnalysis.score` | `analysis.score` | 2025-09-09 | | `RiskAnalysis.timestamp` | `analysis.timestamp` | 2025-09-09 | | `TransactionFilters.assetId_in` | `assetAddress_in` | 2026-02-11 | | `TransactionFilters.assetSymbol_in` | `assetAddress_in` | 2026-02-16 | | `TransactionFilters.marketId_in` | `marketUniqueKey_in` | 2026-02-11 | | `TransactionFilters.userId_in` | `userAddress_in` | 2026-02-11 | | `TransactionFilters.vaultId_in` | `vaultAddress_in` | 2026-02-11 | | `TransactionFilters.assetsUsd_gte` | N/A | 2026-02-16 | | `TransactionFilters.assetsUsd_lte` | N/A | 2026-02-16 | | `TransactionFilters.badDebtAssetsUsd_gte` | N/A | 2026-02-16 | | `TransactionFilters.badDebtAssetsUsd_lte` | N/A | 2026-02-16 | | `TransactionFilters.repaidAssetsUsd_gte` | N/A | 2026-02-16 | | `TransactionFilters.repaidAssetsUsd_lte` | N/A | 2026-02-16 | | `TransactionFilters.seizedAssetsUsd_gte` | N/A | 2026-02-16 | | `TransactionFilters.seizedAssetsUsd_lte` | N/A | 2026-02-16 | | `UsersFilters.assetId_in` | `assetAddress_in` | 2026-02-11 | | `UsersFilters.id_in` | `address_in` | 2026-02-11 | | `UsersFilters.marketId_in` | `marketUniqueKey_in` | 2026-02-11 | | `UsersFilters.vaultId_in` | `vaultAddress_in` | 2026-02-11 | | `Vault.dailyApy` | `state.avgNetApyExcludingRewards(lookback: ONE_DAY)` | 2026-01-15 | | `Vault.dailyApys` | `state.avgNetApyExcludingRewards(lookback: ONE_DAY) and state.avgNetApy(lookback: ONE_DAY)` | 2026-01-15 | | `Vault.monthlyApy` | `state.avgNetApyExcludingRewards(lookback: THIRTY_DAYS)` | 2026-01-15 | | `Vault.monthlyApys` | `state.avgNetApyExcludingRewards(lookback: THIRTY_DAYS) and state.avgNetApy(lookback: THIRTY_DAYS)` | 2026-01-15 | | `Vault.pendingCaps` | `state.pendingConfigs` | 2026-01-15 | | `Vault.promoted` | `featured` | 2026-02-11 | | `Vault.riskAnalysis` | `warnings` | 2025-11-26 | | `Vault.weeklyApys` | `state.avgNetApyExcludingRewards(lookback: SEVEN_DAYS) and state.avgNetApy(lookback: SEVEN_DAYS)` | 2026-01-15 | | `Vault.whitelisted` | `listed` | 2026-02-11 | | `VaultFilters.assetId_in` | `assetAddress_in` | 2026-02-11 | | `VaultFilters.curator_in` | `curatorAddress_in` | 2026-02-11 | | `VaultFilters.id_in` | `address_in` | 2026-02-11 | | `VaultFilteVaultFilters.promoted` | `featured` | 2026-02-11 | | `VaultFilters.whitelisted` | `listed` | 2026-02-11 | | `VaultHistoryDto.curator` | `vault.adminEvents` | 2026-04-20 | | `VaultHistoryDto.feeRecipient` | `vault.adminEvents` | 2026-04-20 | | `VaultHistoryDto.guardian` | `vault.adminEvents` | 2026-04-20 | | `VaultHistoryDto.owner` | `vault.adminEvents` | 2026-04-20 | | `VaultHistoryDto.sharePrice` | `sharePriceNumber` | 2026-01-15 | | `VaultHistoryDto.skimRecipient` | `vault.adminEvents` | 2026-04-20 | | `VaultMetadata.curators` | `state.curators` | 2025-09-09 | | `VaultPosition.assets` | `state.assets` | 2025-09-09 | | `VaultPosition.assetsUsd` | `state.assetsUsd` | 2025-09-09 | | `VaultPosition.shares` | `state.shares` | 2025-09-09 | | `VaultPosition.whitelisted` | `listed` | 2025-12-22 | | `VaultPositionFilters.userId_in` | `userAddress_in` | 2026-01-20 | | `VaultPositionFilters.vaultId_in` | `vaultAddress_in` | 2026-01-20 | | `VaultPositionFilters.`
`vaultWhitelisted` | `vaultListed` | 2026-02-11 | | `VaultReallocateFilters.marketId_in` | `marketUniqueKey_in` | 2026-02-11 | | `VaultReallocateFilters.vaultId_in` | `vaultAddress_in` | 2026-02-11 | | `VaultState.allTimeApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.allTimeNetApy` | `avgNetApy` | 2026-02-15 | | `VaultState.avgApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.biweeklyApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.biweeklyNetApy` | `avgNetApy` | 2026-02-15 | | `VaultState.monthlyApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.monthlyNetApy` | `avgNetApy` | 2026-02-15 | | `VaultState.pendingGuardian` | `pendingConfigs` | 2026-01-15 | | `VaultState.pendingGuardianValidAt` | `pendingConfigs` | 2026-01-15 | | `VaultState.pendingTimelock` | `pendingConfigs` | 2026-01-15 | | `VaultState.pendingTimelockValidAt` | `pendingConfigs` | 2026-01-15 | | `VaultState.quarterlyApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.quarterlyNetApy` | `avgNetApy` | 2026-02-15 | | `VaultState.sharePrice` | `sharePriceNumber` | 2026-01-15 | | `VaultState.yearlyApy` | `avgNetApyExcludingRewards` | 2026-02-15 | | `VaultState.yearlyNetApy` | `avgNetApy` | 2026-02-15 | | `VaultV2.gates` | `gatesConfig` | 2025-11-25 | | `VaultV2.whitelisted` | `listed` | 2025-12-22 | | `VaultV2sFilters.assetId_in` | `address_in` | 2026-02-11 | | `VaultV2sFilters.curator_in` | `curatorAddress_in` | 2025-11-25 | | `VaultV2sFilters.whitelisted` | `listed` | 2026-02-11 |
## August 28, 2026 - Core and Midnight REST API updates [#august-28-2026---core-and-midnight-rest-api-updates]
View change details Implemented on August 28, 2026 The **Core REST API** added the following endpoint:
| API | Change | | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `core` | Added `GET /v0/blue/markets/{market-selector}/liquidity`, which returns `MarketLiquidityResponse` with `market_liquidity`, `reallocatable_liquidity`, `borrow_amount_to_target_utilization`, and `last_indexed_block`. All four fields are bigint-as-string values. |
The **Midnight REST API** expanded the supported `chain_ids` values:
| API | Chain ID | Change | | ---------- | -------- | --------------------------------------------------------------------------------------------------------------- | | `midnight` | `1` | `chain_ids` now supports Ethereum mainnet in addition to Base (`8453`) on endpoints that expose this parameter. |
## August 26, 2026 - Core and Midnight REST API updates [#august-26-2026---core-and-midnight-rest-api-updates]
View change details Implemented on August 26, 2026 The **Core REST API** added the following endpoints:
| API | Change | | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `core` | Added `GET /v0/blue/markets/{market-selector}/users/{user-address}/position/performance`. | | `core` | Added a `404` response to `GET /v0/blue/markets/{market-selector}/users/{user-address}/position`. | | `core` | Added `GET /v0/vaults-v1` and `GET /v0/vaults-v2`. | | `core` | Added `GET /v1/blue/markets`, `GET /v1/vaults-v1/users/{user-address}/positions`, and `GET /v1/vaults-v2/users/{user-address}/positions`. | | `core` | Added the `MarketListResponse`, `UserMarketPositionPerformanceResponse`, `UserVaultPositionsResponse`, `VaultV1ListResponse`, and `VaultV2ListResponse` schemas. | | `core` | Removed `GET /v0/vaults-v1/{vault-selector}/apy` and `GET /v0/vaults-v2/{vault-selector}/apy`. | | `core` | Removed the `VaultApyResponse` schema. |
The **Midnight REST API** changed the following response fields, schemas, status codes, query parameters, and result-set behavior:
| API | Change | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `midnight` | Added `market_family_id` to market detail, market state, and market list responses. | | `midnight` | Added verified nullable Blue callback metadata to each item in `OfferGroupsResponse`, and added `warning` to takeable-offer response structures. | | `midnight` | Added the discriminated `TakeabilityWarningResponse` with warning codes including `MAKER_LIQUIDITY`, `MAKER_ALLOWANCE`, `SOURCE_LIQUIDITY`, `REDUCE_ONLY`, `POSITION_CAPACITY`, `AMOUNT_ROUNDING`, `CALLBACK_NOT_REGISTERED`, `RATIFIER_NOT_AUTHORIZED`, `SIMULATION_FAILED`, `SINGLE_TAKE_LIMIT`, `ROOT_CANCELED`, `ROOT_NOT_RATIFIED`, `SIGNER_NOT_AUTHORIZED`, and `BELOW_MINIMUM_SIZE`. | | `midnight` | Added the `BlueCallbackValidationIssueResponse` and `BlueCallbackRegistryIssueDetailsResponse` schemas, including `callback_not_registered` and `callback_owner_mismatch` registry values. | | `midnight` | Changed shared `ErrorResponse.details` from a nullable object to a nullable array whose items remain untyped. Added typed callback validation details and typed `details` entries to the `MidnightErrorResponse` envelope. | | `midnight` | Added the `market_family_ids` query parameter to `GET /v0/midnight/markets`, `GET /v0/midnight/users/{user-address}/positions`, and `GET /v0/midnight/users/{user-address}/transactions`. It filters by Midnight market family; on positions and transactions, it combines with `market_ids` using AND semantics. | | `midnight` | Added `maturity_gte` and `maturity_lte` to `GET /v0/midnight/books`, and added `status`, `callback_type`, and `market_family_ids` to `GET /v0/midnight/users/{user-address}/offer-groups`. | | `midnight` | Changed the existing `maturities` filter on `GET /v0/midnight/books` from unrestricted numbers to integers from `1` through `9007199254740991`; zero and fractional values are no longer accepted. | | `midnight` | Changed `GET /v0/midnight/books` to return only active maturities admitted by the current policy. Unlisted and past maturities are excluded even when requested with `ids`; books disappear immediately when a maturity leaves the policy, including books with existing offers. | | `midnight` | Under `/v0/midnight`, added `500` responses to `GET /markets`, `/markets/{market-id}`, `/markets/{market-id}/state`, `/markets/{market-id}/transactions`, `/markets/{market-id}/positions`, `/markets/{market-id}/users/{user-address}/position`, `/markets/{market-id}/users/{user-address}/position/performance`, `/users/{user-address}/transactions`, `/users/{user-address}/positions`, and `/users/{user-address}/offer-groups`. | | `midnight` | Under `/v0/midnight`, added `400` responses to `GET /markets/{market-id}`, `/markets/{market-id}/state`, `/markets/{market-id}/users/{user-address}/position`, and `/markets/{market-id}/users/{user-address}/position/performance`. Added `VALIDATION_FAILED` to the `400` response contracts for all ten operations that gained `500` responses. |
## April 23, 2026 - Morpho Vault V1 historical admin fields deprecation [#april-23-2026---morpho-vault-v1-historical-admin-fields-deprecation]
View deprecation details Implemented on April 23, 2026 Announced on April 23, 2026 The jobs backing the admin-address time series on Morpho Vaults V1 have been retired. The following fields remain in the schema for backwards compatibility but are no longer maintained and will be removed at a later stage. **Deprecated fields & alternatives**
| **Field** | **Alternative** | | --------------------------------------------- | ------------------------------------------------------------------------- | | `vault.historicalState.`
`curator` | `vault.adminEvents`
`(where: { type_in: ["setCurator"] })` | | `vault.historicalState.`
`feeRecipient` | `vault.adminEvents`
`(where: { type_in: ["setFeeRecipient"] })` | | `vault.historicalState.`
`guardian` | `vault.adminEvents`
`(where: { type_in: ["setGuardian"] })` | | `vault.historicalState.`
`owner` | `vault.adminEvents`
`(where: { type_in: ["ownershipTransferred"] })` | | `vault.historicalState.`
`skimRecipient` | `vault.adminEvents`
`(where: { type_in: ["setSkimRecipient"] })` |
Apply the same pattern for the other four fields, using the matching `type_in` value and `... on Data` fragment:
| **Event or field** | **Fragment or alternative** | | ---------------------------------------- | ---------------------------------------------------------------- | | `setFeeRecipient` | `SetFeeRecipientEventData { feeRecipient }` | | `setGuardian` | `SetGuardianEventData { guardian }` | | `vault.historicalState.`
`guardian` | `vault.adminEvents`
`(where: { type_in: ["setGuardian"] })` | | `ownershipTransferred` | `OwnershipEventData { owner }` | | `setSkimRecipient` | `SetSkimRecipientEventData { skimRecipient }` |
## May 6, 2026 - Legacy rewards API deprecation [#may-6-2026---legacy-rewards-api-deprecation]
View deprecation details Implemented on May 6th, 2026 Announced on March 13, 2026 Rewards distribution has migrated from the legacy Morpho Universal Rewards Distributor (URD) system to Merkl. All new campaigns from July 2025 onwards use Merkl exclusively.
| Deprecated endpoint | Alternative | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | `rewards.morpho.org/docs` | Deprecated. Historical rewards can still be claimed via
[rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). |
## April 2, 2026 - pnl API fields removal [#april-2-2026---pnl-api-fields-removal]
View removal details Implemented on April 2, 2026 Announced on February 17, 2026 Removed fields: | Removed field | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `vaultPosition.state.roeUsd` | | `vaultPosition.historicalState.`
`pnl` `pnlUsd` `roe` `roeUsd` | | `vaultV2Position.roeUsd` | | `vaultV2Position.historicalState.`
`pnl` `pnlUsd` `roe` `roeUsd` | | `marketPosition.state.`
`pnl` `pnlUsd` `roe` `roeUsd` | | `marketPosition.state.`
`supplyPnl` `supplyPnlUsd` `supplyRoe` `supplyRoeUsd` | | `marketPosition.state.borrowRoeUsd` | | `marketPosition.state.`
`collateralPnlUsd` `collateralRoeUsd` | | `marketPosition.state.`
`marginPnl` `marginPnlUsd` `marginRoe` `marginRoeUsd` | | `marketPosition.historicalState.`
`supplyPnl` `supplyPnlUsd` `supplyRoe` `supplyRoeUsd` | | `marketPosition.historicalState.`
`borrowPnl` `borrowPnlUsd` `borrowRoe` `borrowRoeUsd` | | `marketPosition.historicalState.`
`collateralPnlUsd` `collateralRoeUsd` | | `marketPosition.historicalState.`
`marginPnl` `marginPnlUsd` `marginRoe` `marginRoeUsd` | | `user.state.`
`vaultsPnlUsd` `vaultsRoeUsd` `vaultV2sPnlUsd` `vaultV2sRoeUsd`
`marketsPnlUsd` `marketsRoeUsd` `marketsSupplyPnlUsd` `marketsSupplyRoeUsd`
`marketsBorrowPnlUsd` `marketsBorrowRoeUsd` `marketsCollateralPnlUsd` `marketsCollateralRoeUsd`
`marketsMarginPnlUsd` `marketsMarginRoeUsd` | | `user.historicalState.`
`vaultsPnlUsd` `vaultsRoeUsd` `vaultV2sPnlUsd` `vaultV2sRoeUsd`
`marketsPnlUsd` `marketsRoeUsd` `marketsSupplyPnlUsd` `marketsSupplyRoeUsd`
`marketsBorrowPnlUsd` `marketsBorrowRoeUsd` `marketsCollateralPnlUsd` `marketsCollateralRoeUsd`
`marketsMarginPnlUsd` `marketsMarginRoeUsd` | The fields that will be kept are:
| Kept field | Note | | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `vaultPosition.`
`state.`
`pnl` | — | | `vaultPosition.`
`state.`
`pnlUsd` | changed definition from "p\&l if each interaction was realizing profit/loss in USD" to "currently p\&l in underlying assets quoted in USD at the latest price" | | `vaultPosition.`
`state.`
`roe` | — | | `vaultV2Position.`
`pnl`
`pnlUsd`
`roe` | — | | `marketPosition.`
`state.`
`borrowPnl`
`borrowPnlUsd`
`borrowRoe` | — |
## April 2, 2026 - API fields removal [#april-2-2026---api-fields-removal]
View removal details Implemented on April 2, 2026 Announced on January 26, 2026 This is the first removal notice of the Morpho API. One can expect this kind of removal notice every 6 months. This is a cleanup fields removal, removing only fields that are marked as deprecated for more than 1 year, with an alternative or a non longer maintained feature. **Fields removed & alternatives**
| **Field** | **Alternative** | | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `_crossVersionVaults` | NA | | `marketAverageApys` | `market(s).state` | | `transactionByHash` | NA | | `marketOracleAccuracy` | NA | | `asset.`
`historicalSpotPriceEth` `spotPriceEth` `riskAnalysis` `totalSupply` | NA | | `curators.items.url` | `curators.items.socials.url` | | `market(s).`
`allTimeApys` `dailyApys` `monthlyApys` `quaterlyApys` `weeklyApys` `yearlyApys` | `market(s).state.`
`allTimeBorrowApy` `dailyApys` `monthlyApys` `quaterlyApys` `weeklyApys` `yearlyApys` | | `market(s).collateralPrice` | `market(s).state.price` | | `market(s).concentration` | NA | | `market(s).`
`oracleAddress` `oracleFeed` `oracleInfo` | `market(s).oracle.address`
`market(s).oracle.data...` (×2) | | `vault(s).`
`dailyApy(s)` `monthlyApy(s)` `weeklyApy(s)` `pendingCaps` | `vault(s).state.`
`dailyApy` `monthlyApy` `weeklyApy` `pendingConfigs` | | `marketPosition(s).`
`borrowAssets` `borrowAssetsUsd` `borrowShares` `collateral` `collateralUsd` `supplyAssets` `supplyAssetsUsd` `supplyShares` | `marketPosition(s).state.`
`borrowAssets` `borrowAssetsUsd` `borrowShares` `collateral` `collateralUsd` `supplyAssets` `supplyAssetsUsd` `supplyShares` | | `vaultPosition(s).`
`assets` `assetsUsd` `shares` | `vaultPosition(s).state.`
`assets` `assetsUsd` `shares` | | `on MorphoChainlinkOracleV2Data`
`.baseVault` `.quoteVault`
`on MorphoChainlinkOracleData`
`.vault` | `on MorphoChainlinkOracleV2Data`
`.baseOracleVault` `.quoteOracleVault`
`on MorphoChainlinkOracleData`
`.baseOracleVault` |
Changes represented by `market(s)` / `vault(s)` are also applied to `marketByUniqueKey` and `vaultByAddress` queries.
## October 16, 2025 - Limiting the maximum complexity of a query [#october-16-2025---limiting-the-maximum-complexity-of-a-query]
View change details 16/10/2025 **Action** GraphQL complexity details: | Item | | ---------------------------------------------------------------------------- | | **Max allowed complexity: 1,000,000** | | **Base cost per operation: 10** | | Each field adds cost; some fields declare explicit costs and/or multipliers. | For details on how to check your queries' complexity and reduce it if needed, see [Maximum complexity of a GraphQL query](/developers/api/get-started/#maximum-complexity-of-a-graphql-query).
## September 20, 2025 - Search limitation [#september-20-2025---search-limitation]
View change details 20/09/2025 **Rational** The `numberOfResults` was previously unbounded so the query can yield a huge amount of results which can have an impact on the performance of the Morpho API. **Action**
| Parameter | Constraint | Default | | ----------------- | ------------------------------------------------------- | ------- | | `numberOfResults` | Must be within `[0, 1000]` in the search root resolver. | `5` |
## September 19, 2025 - Fields removal [#september-19-2025---fields-removal]
View removal details 19/09/2025 **Rational** Some rewards fields were wrongly computed since the Merkl integration:
| Affected field | Outcome | | ------------------------ | ------------------------------------------------------- | | `yearlySupplyTokens` | Will always be set to 0 and be removed from the schema. | | `yearlyBorrowTokens` | Will always be set to 0 and be removed from the schema. | | `amountPerSuppliedToken` | Will always be set to 0 and be removed from the schema. | | `amountPerBorrowedToken` | Will always be set to 0 and be removed from the schema. |
**Action** These fields will always be set to 0 and be removed in the schema.
**Troubleshoot** The Morpho API is integrating Merkl, these fields shouldn't be used and instead `supplyApy` and `borrowApy` fields should satisfy integrators needs.
The only removed use-cases along with this 4 fields should be:
| Removed use-case | | --------------------------------------------------------------------- | | displaying rewards for an unpriced asset | | computing the apy with a custom price (fdv feature on the Morpho app) |
--- # Get Started Source: https://docs.morpho.org/developers/api/get-started The Morpho API provides powerful, GraphQL and REST-based interfaces for accessing a comprehensive range of onchain and offchain data from the Morpho ecosystem. It is designed for developers building applications, dashboards, bots, or any service that requires reliable data on Morpho Markets, Morpho Vaults, user positions, and more. The Morpho API is provided as-is, without a Service Level Agreement (SLA). While we strive to maintain reliable service, availability and uptime are not guaranteed. We recommend implementing appropriate fallback mechanisms in production applications and avoiding hard dependencies on API availability for critical operations. The Morpho API can evolve. Stay in touch with updates via the **[API Changelog](/developers/api/changelog/)**. ## Endpoints [#endpoints] **GraphQL API**: For all data related to Blue markets, vaults, and positions. You can explore the schema and run live queries in the [GraphQL Playground](https://api.morpho.org/graphql). **REST API**: For all data related to Blue and Midnight markets, vaults, and positions. You can explore the routes supported in the [API Reference](/api/) page. ## Key Data & Features [#key-data--features] * **Real-time & Historical Data**: Access both current state and historical timeseries data for markets and vaults. * **Market & Vault Analytics**: Retrieve metrics like APYs, TVL, utilization, fees, and market allocations. * **User Positions**: Fetch detailed information on supply, borrow, and collateral positions for any user. * **Rewards**: Query vault and market reward rates and campaign APRs. * **Public Allocator Liquidity**: Query [Vault V2 Public Allocator liquidity](/developers/borrow/tutorials/public-allocator/) available through the Public Allocator. ## Supported Networks [#supported-networks] The API provides data for fully supported deployments on networks including: * **Ethereum** (Chain ID: 1) * **Arbitrum** (Chain ID:42161) * **Base** (Chain ID: 8453) * **HyperEVM** (Chain ID: 999) * **Katana** (Chain ID: 747474) * **Monad** (Chain ID: 143) * **Op Mainnet** (Chain ID: 10) * **Polygon** (Chain ID: 137) * **Robinhood Chain** (Chain ID: 4663) * **Unichain** (Chain ID: 130) * **Stable** (Chain ID: 988) * **Tempo** (Chain ID: 4217) * **World Chain** (Chain ID: 480) ## Morpho API main rules [#morpho-api-main-rules] ### Maximum complexity of a GraphQL query [#maximum-complexity-of-a-graphql-query] A complexity score is associated with each query in the Morpho API. GraphQL complexity details: * **Max allowed complexity: 1,000,000** * Base cost per operation: 10 * Each field adds cost; some fields declare explicit costs and/or multipliers. One can verify its queries' complexity in [https://api.morpho.org/graphql](https://api.morpho.org/graphql). The complexity number is returned as an extension. If one of the query is above the threshold, you have the following alternatives: * **Trim the selection set**: keep only fields you actually use. Prefer cheap snapshots (state.\*) over histories when possible. * **Lower fan-out**: reduce first (e.g., 25 or 50), then paginate. * **Split the work**: fetch a light list first (IDs + a few cheap fields), then run follow-up queries for heavy history on a subset. * **Tune multipliers**: for fields like currentIrmCurve(numberOfPoints: N), lower N. ### Best Practices & Rate Limiting [#best-practices--rate-limiting] To ensure reliable service for all users and protect against malicious usage, spam, and attacks, the API enforces the following rate limits: | Limit | Threshold | Response | | ---------------- | ---------------------------------------------- | -------------------------------------------------- | | **Standard** | 750 requests/minute | `429 Too Many Requests` with `Retry-After: 600` | | **Severe abuse** | \~20,000 requests/hour (3,334 requests/10 min) | `429 Too Many Requests` with `Retry-After: 604800` | Repeated violation of rate limits may result in multi-day cooldown periods or permanent denial. We strongly recommend implementing the following best practices in your integration: * **Cache Responses**: Cache API query results on your backend to reduce redundant requests and improve your application's performance. * **Request Only What You Need**: Structure your GraphQL queries to fetch only the data fields required for your use case. * **Avoid Excessive Polling**: Design your application to fetch data efficiently, respecting API limits to prevent interruptions. The `numberOfResults` parameter in the search root resolver is constrained to be within \[0, 1000] (default value being 5). ### Deprecation Warnings [#deprecation-warnings] API responses may include a `warnings` array in the `extensions` object when a queried field is scheduled for deprecation. We strongly recommend monitoring `response.extensions.warnings` in your integration and setting up alerts so you can migrate away from deprecated fields before they are removed. A warning entry contains all the information you need to update your queries: ```json { "extensions": { "warnings": [ { "type": "DEPRECATED_FIELD", "field": "Vault.whitelisted", "path": "vaultByAddress.whitelisted", "message": "Use listed instead.", "replacement": "listed", "deprecatedAt": "2026-02-11", "removalAt": "2026-05-06" } ] } } ``` | Field | Description | | -------------- | ---------------------------------------------------------------------------- | | `type` | The warning type. `DEPRECATED_FIELD` indicates a field that will be removed. | | `field` | The deprecated field in `Type.fieldName` format. | | `path` | The exact query path where the deprecated field was used. | | `message` | A human-readable description of the deprecation. | | `replacement` | The recommended replacement field. | | `deprecatedAt` | The date the field was deprecated. | | `removalAt` | The date the field will be removed from the API. | Field removals are also communicated via the **[API Changelog](/developers/api/changelog/)**. ## Where to Find Query Examples [#where-to-find-query-examples] This page serves as a high-level overview. For detailed documentation and hands-on query examples, please refer to the dedicated sections below. ### Comprehensive API Documentation [#comprehensive-api-documentation] For a full list of available queries, schemas, and example use cases for each part of the protocol, explore these sections: * [**Morpho Midnight API**](/developers/api/morpho-midnight/) * [**Morpho Blue API**](/developers/api/morpho/) * [**Morpho Vaults API**](/developers/api/morpho-vaults/) * [**Vault V2 Public Allocator guide**](/developers/borrow/tutorials/public-allocator/) * [**Rewards**](/developers/api/rewards/) ### Practical Integration Tutorials [#practical-integration-tutorials] To see how the API is used in practical integration scenarios, check out our step-by-step tutorials: * **Integrating Earn Data**: See the [Get Data for Earn](/developers/earn/tutorials/get-data) tutorial. * **Integrating Borrow Data**: Follow the [Get Data for Borrow](/developers/borrow/tutorials/get-data) guide. * **Using Public Allocator Liquidity**: Follow the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). --- # Morpho Midnight Source: https://docs.morpho.org/developers/api/morpho-midnight {/* AGENT-GENERATED from morpho-midnight.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} ## How to query Morpho Midnight data with the API? [#how-to-query-morpho-midnight-data-with-the-api] Morpho Midnight can be accessed through Morpho's REST API currently under version `v0`. This API is still evolving, and changes to routes and responses may occur as development progresses. ## Discovery & listing [#discovery--listing] ### List all markets [#list-all-markets] Returns all Midnight markets, optionally filtered by chain, loan token, or collateral. Supported `chain_ids`: `1`, `8453`. `GET /v0/midnight/markets` **All markets:** ```bash # All markets on Ethereum mainnet curl "https://api.morpho.org/v0/midnight/markets?chain_ids=1" ``` **Filtered by token:** ```bash # USDC markets on Base with wETH as collateral curl "https://api.morpho.org/v0/midnight/markets\ ?chain_ids=8453\ &loan_assets=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\ &collateral_assets=0x4200000000000000000000000000000000000006" ``` ### Get market detail [#get-market-detail] Immutable configuration for a single market: market family, loan token, maturity, collaterals (with LLTV, liquidation cursor, oracle), and gates. Does **not** include fees or total units (those live on the state endpoint). `GET /v0/midnight/markets/{market-id}` Each entry in the response's `collaterals` list carries exactly these fields: `token`, `lltv`, `liquidation_cursor`, `oracle` ```javascript curl "https://api.morpho.org/v0/midnight/markets/0xcc9418ea594c6e658650aedd205ce4544b266b69493f56fd2adc65c14bd06738" // Response — immutable market definition: { "data": { "chain_id": 8453, "market_id": "0xcc9418ea594c6e658650aedd205ce4544b266b69493f56fd2adc65c14bd06738", "market_family_id": "0x9a6ece688006fb810141501154acfd1aa29ae7ac3753452f4931a092f2653f1f", "loan_token": "0x4200000000000000000000000000000000000006", // WETH on Base "maturity": 1784300400, // July 17, 2026 at 15:00 UTC "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000", "collaterals": [ { "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "lltv": "860000000000000000", "liquidation_cursor": "300000000000000000", "oracle": "0xD09048c8B568Dbf5f189302beA26c9edABFC4858" } ] } } ``` ## Market state & metrics [#market-state--metrics] ### Get market state [#get-market-state] Live market state: total units outstanding, fee configuration, and tick granularity. Separated from the market definition because these values change over time. `GET /v0/midnight/markets/{market-id}/state` ```javascript curl "https://api.morpho.org/v0/midnight/markets/0xcc9418ea594c6e658650aedd205ce4544b266b69493f56fd2adc65c14bd06738/state" // Response: { "data": { "chain_id": 8453, "market_id": "0xcc9418ea594c6e658650aedd205ce4544b266b69493f56fd2adc65c14bd06738", "market_family_id": "0x9a6ece688006fb810141501154acfd1aa29ae7ac3753452f4931a092f2653f1f", "last_indexed_block": "48408699", "total_units": "1481439992020013", // total units outstanding "tick_granularity": 4, // valid ticks must be multiples of this "settlement_fee_schedule": [ { "time_to_maturity_days": 0, "fee_cbp": "0" }, { "time_to_maturity_days": 1, "fee_cbp": "0" }, { "time_to_maturity_days": 7, "fee_cbp": "0" }, { "time_to_maturity_days": 30, "fee_cbp": "0" }, { "time_to_maturity_days": 90, "fee_cbp": "0" }, { "time_to_maturity_days": 180, "fee_cbp": "0" }, { "time_to_maturity_days": 360, "fee_cbp": "0" } ], "current_settlement_fee_wad": "0", "current_settlement_fee_cbp": "0", // interpolated for current TTM "continuous_fee_rate": "0" // per-second, WAD-scaled } } ``` ## Order books & rates [#order-books--rates] ### List policy-admitted books [#list-policy-admitted-books] The endpoint returns only active maturities admitted by the current policy. Unlisted and past maturities stay excluded even when requested with `ids`; if a maturity leaves the policy, its books disappear immediately, including books with existing offers. Each book includes the top 3 ask and bid levels. Sorting: Sort by comma-separated fields (\`id\`, \`ask\`, \`bid\`, \`maturity\`). Prefix a field with \`-\` for descending. Max 3 fields. `GET /v0/midnight/books` **By maturity:** ```bash curl "https://api.morpho.org/v0/midnight/books\ ?chain_ids=8453\ &loan_tokens=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\ &sort=maturity" ``` **By best lend rate:** ```bash # Sort by best ask (lowest price = highest lend rate) curl "https://api.morpho.org/v0/midnight/books\ ?chain_ids=8453\ &loan_tokens=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\ &sort=ask" ``` ### Get full book for a market [#get-full-book-for-a-market] Each level sums all offers at that tick. Levels returned per side: Maximum levels returned per side. Default: 100. Maximum: 5821. `GET /v0/midnight/books/{market-id}` Each price level carries exactly these fields: `tick`, `price`, `units`, `assets`, `count` ```javascript curl "https://api.morpho.org/v0/midnight/books/0xcc9418ea594c6e658650aedd205ce4544b266b69493f56fd2adc65c14bd06738?depth=50" // Response: { "data": { "market_id": "0xcc9418ea594c6e658650aedd205ce4544b266b69493f56fd2adc65c14bd06738", "chain_id": 8453, "midnight": "0x2F7a3AA739ba5792Ce1b4eA046117f2C0095BCA6", "loan_token": "0x4200000000000000000000000000000000000006", "collaterals": [ { "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "lltv": "860000000000000000", "liquidation_cursor": "300000000000000000", "oracle": "0xD09048c8B568Dbf5f189302beA26c9edABFC4858" } ], "maturity": 1784300400, "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000", "asks": [ { "tick": 42, "price": "952380952380952380", "units": "10000000000", "assets": "9523809523", "count": 3 }, { "tick": 43, "price": "948000000000000000", "units": "18500000000", "assets": "17538000000", "count": 5 } ], "bids": [ { "tick": 41, "price": "956937000000000000", "units": "8000000000", "assets": "7655497000", "count": 2 } ] } } ``` Compute rates from prices (TTM = seconds to maturity): ```javascript // lend rate (from asks): (1 / (price / 1e18) - 1) × 365×86400 / TTM // borrow rate (from bids): (1 / (price / 1e18) - 1) × 365×86400 / TTM ``` ### Get one side of the book [#get-one-side-of-the-book] `{side}` is one of `asks`, `bids`. `GET /v0/midnight/books/{market-id}/{side}` ```bash # Just the asks (lender opportunities) curl "https://api.morpho.org/v0/midnight/books/0xd92d...7614/asks?depth=100" # Just the bids (borrower opportunities) curl "https://api.morpho.org/v0/midnight/books/0xd92d...7614/bids?depth=100" ``` ## Quotes & execution [#quotes--execution] ### Get a quote (execution plan) [#get-a-quote-execution-plan] Given a target amount (in assets or units) and a side, the Router returns execution-ready takeable offers sorted by best price with fallback excess. `GET /v0/midnight/books/{market-id}/{side}/quote` Each takeable offer's `offer` struct carries exactly these fields: `market`, `buy`, `maker`, `max_units`, `start`, `expiry`, `tick`, `group`, `callback`, `callback_data`, `receiver_if_maker_is_seller`, `ratifier`, `reduce_only`, `max_assets`, `continuous_fee_cap` **Lend (take asks):** ```javascript // "I want to lend 10,000 USDC" → take asks curl "https://api.morpho.org/v0/midnight/books/0xd92d...7614/asks/quote\ ?assets=10000000000\ &slippage=0.5\ &limit=10000" // Response: { "data": { "average_best_price": "947619047619047619", // realized target fill, using onchain per-offer rounding and rounded against the taker "average_worst_price": "952300000000000000", // conservative adverse-placement bound across returned caps; not the caller's guard "available_assets": "15000000000", "available_units": "15750000000", "takeable_offers": [ { "market_id": "0xd92d...7614", "units": "5000000000", "offer": { "market": { "chain_id": 8453, "midnight": "0x2F7a3AA739ba5792Ce1b4eA046117f2C0095BCA6", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "collaterals": [ { "token": "0x4200000000000000000000000000000000000006", "lltv": "860000000000000000", "liquidation_cursor": "300000000000000000", "oracle": "0x45093658BE7f90B63D7c359e8f408e503c2D9401" } ], "maturity": 1798761600, "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000" }, "buy": false, "maker": "0x7b093658BE7f90B63D7c359e8f408e503c2D9401", "max_units": "0", "start": 1761922790, "expiry": 1798761600, "tick": 42, "group": "0x000000000000000000000000000000000000000000000000000000000008b8f4", "callback": "0x0000000000000000000000000000000000000000", "callback_data": "0x", "receiver_if_maker_is_seller": "0x7b093658BE7f90B63D7c359e8f408e503c2D9401", "ratifier": "0x0000000000000000000000000000000000000002", "reduce_only": false, "max_assets": "5000000000", "continuous_fee_cap": "115792089237316195423570985008687907853269984665640564039457584007913129639935" }, "ratifier_data": "0x4a8b...f201", "warning": null } ] } } ``` **Borrow (take bids):** ```bash # "I want to borrow 5,000 USDC" → take bids curl "https://api.morpho.org/v0/midnight/books/0xd92d...7614/bids/quote\ ?assets=5000000000\ &slippage=0.5\ &limit=10000" # Same response shape — but offers have buy=true (lenders' buy-offers) ``` **By units:** ```bash # Quote by units instead of assets curl "https://api.morpho.org/v0/midnight/books/0xd92d...7614/asks/quote\ ?units=10500000000" ``` ### List takeable offers (raw) [#list-takeable-offers-raw] Individual executable offers with the full offer struct and ratifier data. Use when building custom routing or analysis. By market side: `GET /v0/midnight/books/{market-id}/{side}/takeable-offers` Across markets, by maker: `GET /v0/midnight/takeable-offers` **By market side:** ```bash curl "https://api.morpho.org/v0/midnight/books/0xd92d...7614/asks/takeable-offers" ``` **By maker:** ```bash # All active offers from a specific maker curl "https://api.morpho.org/v0/midnight/takeable-offers\ ?maker=0xMakerAddress...\ &limit=100" ``` ## Position tracking [#position-tracking] ### List user positions [#list-user-positions] Returns all positions for a user across markets. Includes `cost_basis` and `effective_rate_wad` inline. Position `types`: `lend`, `borrow`, `collateral_only`. `GET /v0/midnight/users/{user-address}/positions` **All positions:** ```javascript curl "https://api.morpho.org/v0/midnight/users/0xAlice.../positions" // Response: { "cursor": null, "data": [ { "chain_id": 8453, "market_id": "0xd92d...7614", "user_address": "0xAlice...", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "maturity": 1798761600, "credit": "10500000000", "pending_fee": "12000", "last_loss_factor": "0", "loss_factor": "0", "debt": "0", "cost_basis": "952380952380952380", "effective_rate_wad": "52000000000000000", "collaterals": [], "created_at": 1761922790, "type": "lend" } ] } ``` **Lend only:** ```bash curl "https://api.morpho.org/v0/midnight/users/0xAlice.../positions?types=lend" ``` **Borrow only:** ```bash curl "https://api.morpho.org/v0/midnight/users/0xAlice.../positions?types=borrow" ``` ### Get position detail [#get-position-detail] Raw position state in a specific market. Does **not** include `cost_basis` or `effective_rate_wad` (use the performance endpoint for those). `GET /v0/midnight/markets/{market-id}/users/{user-address}/position` ```javascript curl "https://api.morpho.org/v0/midnight/markets/0xd92d...7614/users/0xBob.../position" // Response (borrow position): { "data": { "chain_id": 8453, "market_id": "0xd92d...7614", "user_address": "0xBob...", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "maturity": 1798761600, "credit": "0", "pending_fee": "0", "last_loss_factor": "0", "loss_factor": "0", "debt": "9000000000", // debt to clear "collaterals": [ { "token": "0x4200000000000000000000000000000000000006", // WETH on Base "amount": "2000000000000000000" // 2 WETH }, { "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base "amount": "10000000" // 10 USDC } ], "type": "borrow", "last_indexed_block": "47457420" } } ``` ### Get position performance [#get-position-performance] Cost basis and effective rate for P\&L computation. Uses average cost accounting. `GET /v0/midnight/markets/{market-id}/users/{user-address}/position/performance` ```javascript curl "https://api.morpho.org/v0/midnight/markets/0xd92d...7614/users/0xAlice.../position/performance" // Response: { "data": { "chain_id": 8453, "market_id": "0xd92d...7614", "user_address": "0xAlice...", "type": "lend", "cost_basis": "952380952380952380", "effective_rate_wad": "52000000000000000", "last_indexed_block": "47457420", "accounting_method": "average_cost" } } ``` ### List all positions in a market [#list-all-positions-in-a-market] Find all borrowers, cross-reference with oracle prices to find unhealthy positions. `GET /v0/midnight/markets/{market-id}/positions` ```bash # All borrowers in a market curl "https://api.morpho.org/v0/midnight/markets/0xd92d...7614/positions?types=borrow" # Active positions only (market not yet matured) curl "https://api.morpho.org/v0/midnight/markets/0xd92d...7614/positions\ ?types=borrow\ &active_only=true" ``` ## Transaction history [#transaction-history] ### User transactions [#user-transactions] All onchain events for a user. Filter by `event_types` to focus on specific actions. Valid values: `borrow`, `lend`, `exit_lend_primary`, `exit_lend_secondary`, `exit_borrow_primary`, `exit_borrow_secondary`, `partial_liquidation`, `full_liquidation`, `withdraw_collateral`, `supply_collateral` {/* The meanings below are human-authored — reconcile this glossary whenever the enum above changes. */} * `lend` — entered a lending position (bought units) * `exit_lend_primary` — redeemed credit at/after maturity * `exit_lend_secondary` — sold credit units early * `borrow` — entered a borrowing position (sold units) * `exit_borrow_primary` — repaid debt * `exit_borrow_secondary` — bought units to close debt early * `supply_collateral` — deposited collateral * `withdraw_collateral` — withdrew collateral * `partial_liquidation` / `full_liquidation` — liquidations; events include the borrower, collateral, seized assets, repaid units, post-maturity mode, bad debt, and latest loss factor `GET /v0/midnight/users/{user-address}/transactions` **Lending activity:** ```bash curl "https://api.morpho.org/v0/midnight/users/0xAlice.../transactions\ ?event_types=lend,exit_lend_primary,exit_lend_secondary" ``` **Borrowing activity:** ```bash curl "https://api.morpho.org/v0/midnight/users/0xBob.../transactions\ ?event_types=borrow,exit_borrow_primary,exit_borrow_secondary,supply_collateral,withdraw_collateral" ``` ### Market transactions [#market-transactions] `GET /v0/midnight/markets/{market-id}/transactions` ```bash # All activity in a market since a specific time curl "https://api.morpho.org/v0/midnight/markets/0xd92d...7614/transactions?created_at_gte=1719000000" ``` ## Offer management [#offer-management] ### List offer groups [#list-offer-groups] Track a maker's consumption groups and which offers belong to them. `status` selects the lifecycle and defaults to `active`; `status=expired` returns groups whose latest offers have all expired or matured. Groups can also be narrowed to verified Blue callbacks with `callback_type=blue_buy`, and to Midnight market families with `market_family_ids`. `GET /v0/midnight/users/{user-address}/offer-groups` ```bash # All active offer groups for this maker (default status) curl "https://api.morpho.org/v0/midnight/users/0xMaker.../offer-groups" # Active offer groups filtered by market and group curl "https://api.morpho.org/v0/midnight/users/0xMaker.../offer-groups\ ?market_ids=0xd92d...7614\ &groups=0xMyGroupId..." # Expired offer groups with verified Blue-buy callbacks curl "https://api.morpho.org/v0/midnight/users/0xMaker.../offer-groups\ ?status=expired\ &callback_type=blue_buy" ``` ## Mempool [#mempool] ### Validate offers before publishing [#validate-offers-before-publishing] Check whether the Router will index your offers. Payload is `version byte + 4-byte big-endian gzip length + gzip(ABI-encoded (Offer, bytes ratifierData)[])`. Optionally pass a `timestamp` to validate against a specific policy snapshot. `POST /v0/midnight/mempool/validate` **Current policy:** ```bash curl -X POST "https://api.morpho.org/v0/midnight/mempool/validate" \ -H "Content-Type: application/json" \ -d '{ "chain_id": 8453, "payload": "0x01..." }' # Success (Router will index): { "data": { "issues": [] } } # Issues found (Router will ignore): { "data": { "issues": [{ "rule": "blue_callback", "details": null }] } } ``` **At a specific time:** ```bash # Validate against the policy snapshot active at a specific time # Uses the latest 15:00 UTC boundary at or before the given timestamp curl -X POST "https://api.morpho.org/v0/midnight/mempool/validate\ ?timestamp=2026-06-28T12:00:00Z" \ -H "Content-Type: application/json" \ -d '{ "chain_id": 8453, "payload": "0x01..." }' ``` ## Pagination & filtering [#pagination--filtering] List endpoints support cursor-based pagination. The response includes a `cursor` field — pass it as a query parameter to get the next page. ```javascript # First page curl "https://api.morpho.org/v0/midnight/markets?chain_ids=8453" // Response includes: { "cursor": "***...", "data": [...] } # Next page — pass the cursor curl "https://api.morpho.org/v0/midnight/markets?chain_ids=8453&cursor=***..." // cursor is null when there are no more pages ``` The markets list carries the richest filter set — other list endpoints follow the same conventions: | Name | In | Type | Required | Description | | ------------------- | ----- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `chain_ids` | query | array | No | Filter by chain ids. Supported chains: Ethereum (1) and Base (8453). Required when any address filter is provided. | | `market_ids` | query | array | No | Filter by Midnight market ids. These ids are globally unique. | | `market_family_ids` | query | array | No | Filter by Midnight market family ids. A family contains markets whose immutable parameters differ only by maturity. | | `loan_assets` | query | array | No | Filter by loan token addresses. Combine with chain\_ids; lowercase input is checksummed. | | `collateral_assets` | query | array | No | Filter by collateral token addresses. Combine with chain\_ids; lowercase input is checksummed. | | `oracle_addresses` | query | array | No | Filter by collateral oracle addresses. Combine with chain\_ids; lowercase input is checksummed. | | `enter_gates` | query | array | No | Filter by enter gate addresses. Combine with chain\_ids; lowercase input is checksummed. | | `liquidator_gates` | query | array | No | Filter by liquidator gate addresses. Combine with chain\_ids; lowercase input is checksummed. | | `maturities` | query | array | No | Filter by exact maturity timestamps, in unix seconds. | | `active_only` | query | string | No | When true, enforces maturity strictly after the current timestamp. | | `maturity_gte` | query | anyOf | No | Filter for maturities greater than or equal to this unix timestamp. | | `maturity_lte` | query | anyOf | No | Filter for maturities less than or equal to this unix timestamp. | | `total_units_gte` | query | string | No | Filter for total units greater than or equal to this value. | | `total_units_lte` | query | string | No | Filter for total units less than or equal to this value. | | `listed` | query | string | No | Filter by listing status. When true, returns only markets surfaced by the app trust layer; when false, only unlisted markets. Omit to return both. | | `sort_by` | query | string | No | Sort field for cursor pagination. | | `sort_direction` | query | string | No | Sort direction for cursor pagination. | | `limit` | query | integer | No | Maximum number of items to return. | | `cursor` | query | string | No | Opaque cursor from a previous page response. | --- # Morpho Vaults Source: https://docs.morpho.org/developers/api/morpho-vaults ## How to query Morpho Vaults Data thanks to the API? [#how-to-query-morpho-vaults-data-thanks-to-the-api] Each example below is available as **REST** (selected by default) or **GraphQL** — use the tabs to switch. * **REST** calls use public `GET` requests against `https://api.morpho.org` with standard public rate limits. Entities are addressed by a `:
` selector (e.g. `1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB`). REST returns single-entity reads — config, state, APY averages and history, allocation history, and positions — and exposes listed-vault discovery through `GET /v0/vaults-v1` and `GET /v0/vaults-v2`, plus user-position lists through `GET /v1/vaults-v1/users/{user-address}/positions` and `GET /v1/vaults-v2/users/{user-address}/positions`; the V2 position list excludes fee wrappers, while unlisted-vault discovery, USD-denominated values, and reward APRs come from **GraphQL**. * **GraphQL** queries run against the Playground at [https://api.morpho.org/graphql](https://api.morpho.org/graphql) — paste any query below and run it. ## Discovery and Listing [#discovery-and-listing] ### Vaults List [#vaults-list] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2?chain_ids=1&limit=100" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1?chain_ids=1&limit=100" \ -H "accept: application/json" ``` `GET /v0/vaults-v1` and `GET /v0/vaults-v2` return cursor-paginated listed-vault directories. These lists are not complete enumerations of indexed vaults: V1 membership is listing-gated, and V2 returns listed instances only. Use GraphQL to include unlisted vaults. ```graphql query { vaultV2s(first: 1000, where: { chainId_in: [1, 8453] }) { items { address symbol name listed asset { id address decimals } chain { id network } } } } ``` ```graphql query { vaults(first: 1000, where: { chainId_in: [1, 8453] }) { items { address symbol name listed asset { id address decimals } chain { id network } } } } ``` ## Vault Metrics [#vault-metrics] ### Total Deposits & Assets [#total-deposits--assets] #### All Vaults [#all-vaults] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/state" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/state" \ -H "accept: application/json" ``` REST returns one vault's totals. Enumerate listed vaults with `GET /v0/vaults-v1` or `GET /v0/vaults-v2`, then call `/state` for each vault; use GraphQL for unlisted vaults and USD-denominated values. ```graphql query { vaultV2s(first: 100) { items { address totalAssets totalAssetsUsd totalSupply liquidityUsd idleAssetsUsd } } } ``` ```graphql query { vaults(first: 100, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address state { totalAssets totalAssetsUsd totalSupply } } } } ``` #### Specific Vault [#specific-vault] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/state" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/state" \ -H "accept: application/json" ``` Returns `total_assets`, `total_supply`, and `withdrawable_assets`; V2 also returns `allocated_assets` and `idle_assets`. USD-denominated values are GraphQL-only. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address totalAssets totalAssetsUsd totalSupply liquidity liquidityUsd idleAssetsUsd } } ``` ```graphql query { vaultByAddress( address: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" chainId: 1 ) { address state { totalAssets totalAssetsUsd totalSupply } liquidity { underlying usd } } } ``` ### APY (Native + Rewards) [#apy-native--rewards] #### All Vaults [#all-vaults-1] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/apy-averages?lookback=six_hours" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/apy-averages?lookback=six_hours" \ -H "accept: application/json" ``` `/apy-averages` returns the realized average `apy` from share-price evolution over the selected `lookback` (post-fee, excluding rewards). REST calculates one vault at a time; use the listed-vault routes for listed discovery, and GraphQL for unlisted vaults and reward APRs. ```graphql query { vaultV2s(first: 10) { items { address asset { yield { apr } } avgNetApyExcludingRewards avgNetApy performanceFee managementFee maxRate rewards { asset { address chain { id } } supplyApr } } } } ``` ```graphql query { vaults(first: 100, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address asset { yield { apr } } state { apy netApy netApyExcludingRewards avgNetApyExcludingRewards avgNetApy dailyApy: avgNetApyExcludingRewards(lookback: ONE_DAY) dailyNetApy: avgNetApy(lookback: ONE_DAY) weeklyApy: avgNetApyExcludingRewards(lookback: SEVEN_DAYS) weeklyNetApy: avgNetApy(lookback: SEVEN_DAYS) monthlyApy: avgNetApyExcludingRewards(lookback: THIRTY_DAYS) monthlyNetApy: avgNetApy(lookback: THIRTY_DAYS) allRewards { asset { address chain { id } } supplyApr } allocation { supplyAssets supplyAssetsUsd market { marketId state { rewards { asset { address chain { id } } supplyApr borrowApr } } } } } } } } ``` #### Specific Vault [#specific-vault-1] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/apy-averages?lookback=six_hours" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/apy-averages?lookback=six_hours" \ -H "accept: application/json" ``` `/apy-averages` returns the realized average `apy` from share-price evolution over `?lookback=` (post-fee, **excluding rewards**). Reward APRs and per-market allocation breakdowns are GraphQL-only. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address asset { yield { apr } } avgNetApyExcludingRewards avgNetApy performanceFee managementFee maxRate rewards { asset { address chain { id } } supplyApr } } } ``` ```graphql query { vaultByAddress( address: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" chainId: 1 ) { address asset { yield { apr } } state { apy netApy netApyExcludingRewards avgNetApyExcludingRewards avgNetApy dailyApy: avgNetApyExcludingRewards(lookback: ONE_DAY) dailyNetApy: avgNetApy(lookback: ONE_DAY) weeklyApy: avgNetApyExcludingRewards(lookback: SEVEN_DAYS) weeklyNetApy: avgNetApy(lookback: SEVEN_DAYS) monthlyApy: avgNetApyExcludingRewards(lookback: THIRTY_DAYS) monthlyNetApy: avgNetApy(lookback: THIRTY_DAYS) allRewards { asset { address chain { id } } supplyApr } allocation { supplyAssets supplyAssetsUsd market { marketId state { rewards { asset { address chain { id } } supplyApr borrowApr } } } } } } } ``` ### Share Price (Token Value) [#share-price-token-value] #### All Vaults [#all-vaults-2] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/state" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/state" \ -H "accept: application/json" ``` `/state` returns `share_price_ray` — the normalized share price scaled by RAY (1e27), i.e. one whole share quoted in whole underlying tokens. Use the REST lists to enumerate listed vaults; unlisted-vault discovery and USD-denominated share price are GraphQL-only. ```graphql query { vaultV2s(first: 10) { items { address totalSupply totalAssets sharePrice } } } ``` ```graphql query { vaults(first: 100, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address state { sharePriceNumber sharePriceUsd } } } } ``` #### Specific Vault [#specific-vault-2] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/state" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/state" \ -H "accept: application/json" ``` `/state` returns `share_price_ray` — the normalized share price scaled by RAY (1e27), i.e. one whole share quoted in whole underlying tokens. USD-denominated share price is GraphQL-only. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { totalAssets totalSupply sharePrice } } ``` ```graphql query { vaultByAddress( address: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" chainId: 1 ) { address state { sharePriceNumber sharePriceUsd } } } ``` ## Configuration & Curation [#configuration--curation] ### Vault Parameters [#vault-parameters] #### All Vaults [#all-vaults-3] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" \ -H "accept: application/json" ``` Returns the core on-chain config (`owner`, `curator`, `timelock_seconds`, fees; V1 adds `guardian`; V2 adds `adapter_registry`, `liquidity_adapter`, `gates`). REST lists enumerate listed vaults only. Use GraphQL for unlisted-vault discovery, allocator lists, curator metadata, `listed` status, and free-form metadata. ```graphql query { vaultV2s(first: 10) { items { address name listed metadata { description image } allocators { allocator { address } } owner { address } curators { items { addresses { address } } } sentinels { sentinel { address } } timelocks { duration selector functionName } } } } ``` ```graphql query { vaults(first: 100, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address name listed metadata { description } allocators { address } state { owner curator guardian timelock curators { name socials { type url } image } } } } } ``` #### Specific Vault [#specific-vault-3] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" \ -H "accept: application/json" ``` Returns the core on-chain config: `owner`, `curator`, `timelock_seconds`, and fee setup (V1 adds `guardian`; V2 adds `adapter_registry`, `liquidity_adapter`, and `gates`). The allocators list, curator metadata, sentinels, `listed` status, and free-form `metadata` (description/image) are GraphQL-only. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address name listed metadata { description image } allocators { allocator { address } } owner { address } curators { items { addresses { address } } } sentinels { sentinel { address } } timelocks { duration selector functionName } } } ``` ```graphql query { vaultByAddress( address: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" chainId: 1 ) { address name listed metadata { description } allocators { address } state { owner curator guardian timelock curators { name socials { type url } image } } } } ``` ### Roles: Allocators & Sentinels [#roles-allocators--sentinels] ```bash # Vault config curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" \ -H "accept: application/json" # Allocators curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/allocators" \ -H "accept: application/json" # Sentinels curl -sS "https://api.morpho.org/v1/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/sentinels" \ -H "accept: application/json" ``` `/allocators` and `/sentinels` are cursor-paginated (`cursor`, default `limit=100`, max 1000) and return `data[].address` plus `data[].tx_hash` of the most recent role grant. ```bash # Vault config curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" \ -H "accept: application/json" # Allocators curl -sS "https://api.morpho.org/v1/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/allocators" \ -H "accept: application/json" ``` `/allocators` is cursor-paginated (`cursor`, default `limit=100`, max 1000) and returns `data[].address` plus `data[].tx_hash` of the most recent role grant. Morpho Vaults V1 has no sentinel role. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address name allocators { allocator { address } } sentinels { sentinel { address } } } } ``` ```graphql query { vaultByAddress( address: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" chainId: 1 ) { address name allocators { address } } } ``` ## Fee Wrapper [#fee-wrapper] A Fee Wrapper is a Vault V2 configured in a specific, constrained mode that enables distribution channels to add a fee layer on top of an existing Morpho Vault V2. ### FeeWrapper List [#feewrapper-list] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2/8453:0xd4468EF3745c315949a97090eD27b3F73b9b7C02" \ -H "accept: application/json" ``` A fee wrapper is a Vault V2 — read it by `:
` via the V2 endpoint. Listing all fee wrappers (filter by type) is GraphQL-only. ```graphql query VaultV2ByAddress { vaultV2ByAddress( address: "0xd4468EF3745c315949a97090eD27b3F73b9b7C02" chainId: 8453 ) { type address name } } ``` ```graphql query VaultV2s { vaultV2s(skip: 0, first: 20, where: { type_in: [FeeWrapper] }) { items { address name } } } ``` ### FeeWrapper Yield [#feewrapper-yield] ```bash # Config (performance / management fee setup) curl -sS "https://api.morpho.org/v0/vaults-v2/8453:0xd4468EF3745c315949a97090eD27b3F73b9b7C02" \ -H "accept: application/json" # Realized average APY curl -sS "https://api.morpho.org/v1/vaults-v2/8453:0xd4468EF3745c315949a97090eD27b3F73b9b7C02/apy-averages?lookback=six_hours" \ -H "accept: application/json" ``` The config route (`GET /v0/vaults-v2/{selector}`) returns `performance_fee_wad`, `performance_fee_recipient`, `management_fee_wad`, and `management_fee_recipient`; `/apy-averages` returns realized average APY for the selected lookback. Reward APRs and historical share price are GraphQL-only. ```graphql query VaultV2ByAddress { vaultV2ByAddress( address: "0xd4468EF3745c315949a97090eD27b3F73b9b7C02" chainId: 8453 ) { type apy avgNetApyExcludingRewards avgNetApy performanceFee performanceFeeRecipient managementFee managementFeeRecipient rewards { asset { symbol address } supplyApr } } } ``` ```graphql query VaultV2s { vaultV2s(skip: 0, first: 20, where: { type_in: [FeeWrapper] }) { items { type address apy avgNetApyExcludingRewards avgNetApy performanceFee performanceFeeRecipient managementFee managementFeeRecipient rewards { asset { symbol address } supplyApr } } } } ``` ## Position Tracking [#position-tracking] ### Vault Depositors [#vault-depositors] #### Specific Vault [#specific-vault-4] ```bash curl -sS "https://api.morpho.org/v1/vaults-v2/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/positions?chain_ids=1&vault_addresses=0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v1/vaults-v1/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/positions?chain_ids=1&vault_addresses=0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" \ -H "accept: application/json" ``` REST returns a single user's position in a vault; the full depositor list is GraphQL-only. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { positions(first: 10, skip:0) { items { user { address } assets assetsUsd shares } } totalSupply asset { address symbol } } } ``` ```graphql query { vaultPositions( first: 10 orderBy: Shares orderDirection: Desc where: { vaultAddress_in: ["0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB"] } ) { items { user { address } state { shares assets assetsUsd } } } } ``` #### Set of Vaults [#set-of-vaults] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position" \ -H "accept: application/json" ``` REST reads one (vault, user) position per request; use `GET /v1/vaults-v1/users/{user-address}/positions` or `GET /v1/vaults-v2/users/{user-address}/positions` to query a set of vaults at once. The V2 list excludes fee wrappers. ```graphql query { vaultV2s( first: 10 where: { chainId_in: [1] address_in: [ "0xb576765fB15505433aF24FEe2c0325895C559FB2" "0x6dC58a0FdfC8D694e571DC59B9A52EEEa780E6bf" ] } ) { items { address name positions(first: 10, skip: 0) { items { user { address } assets assetsUsd shares } pageInfo { count countTotal skip limit } } } pageInfo { count countTotal skip limit } } } ``` ```graphql query { vaultPositions( first: 10 orderBy: Shares orderDirection: Desc where: { vaultAddress_in: [ "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB" "0x73e65DBD630f90604062f6E02fAb9138e713edD9" ] } ) { items { vault { address } user { address } state { shares assets assetsUsd } } } } ``` ### User Positions [#user-positions] #### User All Vaults Position [#user-all-vaults-position] ```bash # Vault V2 positions curl -sS "https://api.morpho.org/v1/vaults-v2/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/positions?chain_ids=1&active_only=true&limit=100" \ -H "accept: application/json" # Vault V1 positions curl -sS "https://api.morpho.org/v1/vaults-v1/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/positions?chain_ids=1&active_only=true&limit=100" \ -H "accept: application/json" ``` REST lists every active vault position a user holds, separately for V1 and V2, with cursor pagination. The V2 list excludes fee wrappers. ```graphql query GetUserVaultPositions($address: String!, $chainId: Int!) { userByAddress(address: $address, chainId: $chainId) { address chain { id } vaultV2Positions { shares vault { address symbol } } vaultPositions { state { shares } vault { address symbol } } } } ``` **Query Variables example:** ```json { "address": "USER_ADDRESS", "chainId": 8453 } ``` #### User Market Position [#user-market-position] ```bash # Vault V2 positions curl -sS "https://api.morpho.org/v1/vaults-v2/users/0x821880a3E2bac432d67E5155e72BB655Ef65fa5E/positions?chain_ids=1&active_only=true&limit=100" \ -H "accept: application/json" # Vault V1 positions curl -sS "https://api.morpho.org/v1/vaults-v1/users/0x821880a3E2bac432d67E5155e72BB655Ef65fa5E/positions?chain_ids=1&active_only=true&limit=100" \ -H "accept: application/json" ``` REST lists a user's vault positions by version; the V2 list excludes fee wrappers. Market positions still require explicit market IDs, and a combined cross-vault/cross-market overview and transaction history are GraphQL-only. ```graphql query { userByAddress( chainId: 1 address: "0x821880a3E2bac432d67E5155e72BB655Ef65fa5E" ) { address marketPositions { market { marketId } state { borrowAssets borrowAssetsUsd supplyAssets supplyAssetsUsd } } vaultPositions { vault { address name } state { assets assetsUsd shares } } vaultV2Positions { vault { address name } assets assetsUsd shares } } vaultV1Transactions( first: 10 orderBy: Time orderDirection: Desc where: { userAddress_in: ["0x821880a3E2bac432d67E5155e72BB655Ef65fa5E"] chainId_in: [1] } ) { items { txHash timestamp type } } marketTransactions( first: 10 orderBy: Timestamp orderDirection: Desc where: { userAddress_in: ["0x821880a3E2bac432d67E5155e72BB655Ef65fa5E"] chainId_in: [1] } ) { items { txHash timestamp type } } vaultV2transactions( first: 10 orderBy: Time orderDirection: Desc where: { userAddress_in: ["0x821880a3E2bac432d67E5155e72BB655Ef65fa5E"] chainId_in: [1] } ) { items { txHash timestamp type shares assets vault { address name } } } } ``` ### User Earnings [#user-earnings] #### Earnings Across All Vaults [#earnings-across-all-vaults] ```bash # Vault V2 performance curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position/performance" \ -H "accept: application/json" # Vault V1 performance curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position/performance" \ -H "accept: application/json" ``` `/position/performance` returns per-vault `net_earnings_assets` (≈ pnl) and `return_on_capital` (≈ roe); the aggregate across all vaults is GraphQL-only. ```graphql query { userByAddress(address: "USERADDRESS", chainId: 1) { vaultV2Positions { vault { name address } assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } vaultPositions { vault { name address } state { assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } } } } ``` #### Earnings on a Specific Vault [#earnings-on-a-specific-vault] ```bash # Position: shares + assets curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position" \ -H "accept: application/json" # Performance: net earnings + return on capital curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position/performance" \ -H "accept: application/json" ``` ```bash # Position: shares + assets curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position" \ -H "accept: application/json" # Performance: net earnings + return on capital curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position/performance" \ -H "accept: application/json" ``` `/position` returns `shares` and `assets`; `/position/performance` returns `net_earnings_assets` (≈ `pnl`) and `return_on_capital` (≈ `roe` — time-weighted since inception, non-annualized). USD-denominated values are GraphQL-only. ```graphql query { vaultV2PositionByAddress( userAddress: "USERADDRESS" vaultAddress: "MORPHOVAULTV2ADDRESS" chainId: 1 ) { vault { name address } assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } } ``` ```graphql query { vaultPositions(where: { userAddress_in: ["USERADDRESS"] vaultAddress_in: ["MORPHOVAULTV1ADDRESS"] chainId_in: [1] }) { items { vault { name address } state { assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } } } } ``` ## Historical Data [#historical-data] ### APY Historical State [#apy-historical-state] #### All Vaults [#all-vaults-4] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/apy/history?lookback=thirty_days" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB/apy/history?lookback=thirty_days" \ -H "accept: application/json" ``` REST exposes trailing history via fixed `lookback` windows (`one_day`, `seven_days`, `thirty_days`, `ninety_days`, `one_year`, `inception`) and is per-vault. Use the REST lists for listed-vault discovery; unlisted-vault discovery and cross-vault history queries are GraphQL-only. ```graphql query VaultV2Apys($options: TimeseriesOptions) { vaultV2s(first: 10, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address name historicalState { avgApy(options: $options, lookbackHours: 24) { x y } avgNetApy(options: $options, lookbackHours: 24) { x y } } } } } ``` ```graphql query VaultApys($options: TimeseriesOptions) { vaults(first: 10, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address name historicalState { apy(options: $options) { x y } netApy(options: $options) { x y } } } } } ``` #### Specific Vault [#specific-vault-5] ```bash curl -sS "https://api.morpho.org/v0/vaults-v2/1:0x04422053aDDbc9bB2759b248B574e3FCA76Bc145/apy/history?lookback=thirty_days" \ -H "accept: application/json" ``` ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0x73e65DBD630f90604062f6E02fAb9138e713edD9/apy/history?lookback=thirty_days" \ -H "accept: application/json" ``` REST exposes trailing history via fixed `lookback` windows (`one_day`, `seven_days`, `thirty_days`, `ninety_days`, `one_year`, `inception`) rather than an arbitrary `startTimestamp`/`endTimestamp`/`interval`. ```graphql query VaultV2Apys($options: TimeseriesOptions) { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address name historicalState { avgApy(options: $options, lookbackHours: 24) { x y } avgNetApy(options: $options, lookbackHours: 24) { x y } } } } ``` ```graphql query VaultApys($options: TimeseriesOptions) { vaultByAddress(address: "0x73e65DBD630f90604062f6E02fAb9138e713edD9") { address name historicalState { apy(options: $options) { x y } netApy(options: $options) { x y } } } } ``` **Query Variables:** ```json { "options": { "startTimestamp": 1768211594, "endTimestamp": 1768384394, "interval": "DAY" } } ``` ### Historical Allocation (Morpho Vaults V1) [#historical-allocation-morpho-vaults-v1] ```bash curl -sS "https://api.morpho.org/v0/vaults-v1/1:0x73e65DBD630f90604062f6E02fAb9138e713edD9/allocations/history?lookback=thirty_days" \ -H "accept: application/json" ``` REST returns per-market allocation history for a **single** vault via fixed `lookback` windows; append `&market_id=` to filter to one market. Listing allocations across all vaults and USD-denominated series are GraphQL-only. ```graphql query Vaults($options: TimeseriesOptions) { vaults (first: 10, skip: 0, orderBy: TotalAssetsUsd, orderDirection: Desc) { items { address name historicalState { allocation { market { marketId } supplyAssets(options: $options) { x y } supplyAssetsUsd(options: $options) { x y } } } } } } ``` ```graphql query VaultApys($options: TimeseriesOptions) { vaultByAddress(address: "0x73e65DBD630f90604062f6E02fAb9138e713edD9") { address historicalState { allocation { market { marketId } supplyAssets(options: $options) { x y } supplyAssetsUsd(options: $options) { x y } } } } } ``` **Query Variables:** ```json { "options": { "startTimestamp": 1768211594, "endTimestamp": 1768384394, "interval": "DAY" } } ``` --- # Morpho Blue Source: https://docs.morpho.org/developers/api/morpho ## How to query Morpho Blue data with the API? [#how-to-query-morpho-blue-data-with-the-api] Each example below is available as **REST** (selected by default) or **GraphQL** — use the tabs to switch. * **REST** calls use public `GET` requests against `https://api.morpho.org` with standard public rate limits. Markets are addressed by a `:` selector and tokens by `:
`. REST provides single-entity reads, a cursor-paginated market list through `GET /v1/blue/markets`, and versioned lists of a user's vault positions. Aggregate cross-market overviews, oracle feed composition, USD values, and reward APRs still come from **GraphQL**. * **GraphQL** queries run against the Playground at [https://api.morpho.org/graphql](https://api.morpho.org/graphql). By default the API returns only the first 100 results and reads from the Ethereum network. ## Discovery and Listing [#discovery-and-listing] ### Markets List [#markets-list] ```bash curl -sS "https://api.morpho.org/v1/blue/markets?chain_id=8453&listed=true&limit=100" \ -H "accept: application/json" ``` REST lists markets with cursor pagination and supports `chain_id`, `loan_token`, `collateral_token`, and `listed` filters. It does not expose the GraphQL API's broader sorting and aggregate filters. ```graphql query { markets { items { marketId lltv oracle { address } irmAddress loanAsset { address symbol decimals } collateralAsset { address symbol decimals } state { borrowAssets supplyAssets fee utilization } } } } ``` ```graphql query { markets(where: { listed: true }) { items { marketId listed lltv oracle { address } irmAddress loanAsset { address symbol decimals } collateralAsset { address symbol decimals } state { borrowAssets supplyAssets fee utilization } } } } ``` ### Market Parameters [#market-parameters] ```bash curl -sS "https://api.morpho.org/v0/blue/markets/8453:0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" \ -H "accept: application/json" ``` REST reads one market at a time by `:`. The response gives the immutable config: `loan_token`, `collateral_token`, `oracle_address`, `irm_address`, and `lltv_wad`. Use `GET /v1/blue/markets` to enumerate and filter across markets by `chain_id`, `loan_token`, `collateral_token`, and `listed`. ```graphql query { markets( first: 100 orderBy: SupplyAssetsUsd orderDirection: Desc where: { chainId_in: [1, 8453] } ) { items { marketId loanAsset { address } collateralAsset { address } lltv irmAddress oracle { address } } } } ``` ```graphql query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { marketId loanAsset { address } collateralAsset { address } lltv irmAddress oracle { address } } } ``` ## Market Metrics [#market-metrics] ### Total Collateral, Borrow & Supply [#total-collateral-borrow--supply] ```bash curl -sS "https://api.morpho.org/v0/blue/markets/8453:0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836/state" \ -H "accept: application/json" ``` `/state` returns `total_supply_assets`, `total_borrow_assets`, their share counts (`total_supply_shares`, `total_borrow_shares`), and `fee_wad`. USD-denominated values and aggregate collateral totals are GraphQL-only. ```graphql query { markets( first: 100 orderBy: SupplyAssetsUsd orderDirection: Desc where: { chainId_in: [1, 8453] } ) { items { marketId state { collateralAssets collateralAssetsUsd borrowAssets borrowAssetsUsd supplyAssets supplyAssetsUsd liquidityAssets liquidityAssetsUsd } } } } ``` ```graphql query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { state { collateralAssets borrowAssets supplyAssets liquidityAssets } } } ``` ### Market APY (Native & Rewards) [#market-apy-native--rewards] ```bash curl -sS "https://api.morpho.org/v0/blue/markets/8453:0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836/apy-averages" \ -H "accept: application/json" ``` `/apy-averages` returns `supply_apy_averages` and `borrow_apy_averages`, each keyed by fixed trailing windows (`24h`, `7d`, `30d`, `90d`, `1y`). Values **exclude rewards** — reward APRs are GraphQL-only. ```graphql query { markets( first: 100 orderBy: SupplyAssetsUsd orderDirection: Desc where: { chainId_in: [1, 8453] } ) { items { marketId state { borrowApy avgBorrowApy avgNetBorrowApy supplyApy avgSupplyApy avgNetSupplyApy rewards { asset { address chain { id } } supplyApr borrowApr } } } } } ``` ```graphql query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { state { borrowApy avgBorrowApy avgNetBorrowApy supplyApy avgSupplyApy avgNetSupplyApy rewards { supplyApr borrowApr } } } } ``` ## Asset Information [#asset-information] ### Assets (Price & Yield) [#assets-price--yield] ```bash # Token identity + metadata curl -sS "https://api.morpho.org/v0/tokens/1:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" \ -H "accept: application/json" # Canonical USD spot price curl -sS "https://api.morpho.org/v0/tokens/1:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0/price" \ -H "accept: application/json" ``` REST reads one token at a time by `:
`: `/tokens/{selector}` for identity/metadata and `/tokens/{selector}/price` for the USD spot price (`price`, `timestamp`, `provider`). Native yield (`yield.apr`) and multi-asset symbol filtering are GraphQL-only. ```graphql query GetAssetsWithPriceAndYield { assets(where: { symbol_in: ["wstETH", "WETH"], chainId_in: [1] }) { items { symbol address price(maxLag: 12) { usd timestamp } yield { apr lookback } chain { id network } } } } ``` * `price.usd` is the current price of the asset in USD. * `price.timestamp` is when the returned price was recorded. * `maxLag` is the maximum lookback window in hours, from `0` to `24`; default is `12`. * `yield.apr` is the asset APR, when available. * `yield.lookback` is the lookback period used to compute the APR, in seconds. * `yield` can be `null` when no native-yield data is available. * USD prices are sourced from DefiLlama today, so availability and freshness can depend on DefiLlama coverage for the asset. ## Position Tracking [#position-tracking] ### User Positions [#user-positions] #### User All Markets Position [#user-all-markets-position] ```bash # Current market position curl -sS "https://api.morpho.org/v0/blue/markets/1:0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position" \ -H "accept: application/json" # Realized and unrealized position performance curl -sS "https://api.morpho.org/v0/blue/markets/1:0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position/performance" \ -H "accept: application/json" ``` REST returns a **single** user's position in a market — `collateral_assets`, `supply_shares`, and `borrow_shares` — keyed by `:` and user address. The performance route adds `supply_net_earnings_assets`, `supply_return_on_capital`, `borrow_net_cost_assets`, and `borrow_cost_on_capital` under average-cost accounting. Listing every position in a market, and USD-denominated values, are GraphQL-only. ```graphql query { marketPositions( first: 10, orderBy: BorrowShares, orderDirection: Desc where: { marketUniqueKey_in: ["0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115"] } ) { items { user { address } state { collateral borrowAssets borrowAssetsUsd } } } } ``` #### User Market Position [#user-market-position] ```bash # Market position curl -sS "https://api.morpho.org/v0/blue/markets/1:0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115/users/0x821880a3E2bac432d67E5155e72BB655Ef65fa5E/position" \ -H "accept: application/json" # Vault V2 positions curl -sS "https://api.morpho.org/v1/vaults-v2/users/0x821880a3E2bac432d67E5155e72BB655Ef65fa5E/positions?chain_ids=1&active_only=true&limit=100" \ -H "accept: application/json" ``` REST reads market positions one market at a time and lists a user's vault positions separately for V1 and V2. A combined cross-market/cross-vault overview and transaction history are GraphQL-only. ```graphql query { userByAddress( chainId: 1 address: "0x821880a3E2bac432d67E5155e72BB655Ef65fa5E" ) { address marketPositions { market { marketId } state { borrowAssets borrowAssetsUsd supplyAssets supplyAssetsUsd } } vaultPositions { vault { address name } state { assets assetsUsd shares } } vaultV2Positions { vault { address name } assets assetsUsd shares } } vaultV1Transactions( first: 10 orderBy: Time orderDirection: Desc where: { userAddress_in: ["0x821880a3E2bac432d67E5155e72BB655Ef65fa5E"] chainId_in: [1] } ) { items { txHash timestamp type } } marketTransactions( first: 10 orderBy: Timestamp orderDirection: Desc where: { userAddress_in: ["0x821880a3E2bac432d67E5155e72BB655Ef65fa5E"] chainId_in: [1] } ) { items { txHash timestamp type } } vaultV2transactions( first: 10 orderBy: Time orderDirection: Desc where: { userAddress_in: ["0x821880a3E2bac432d67E5155e72BB655Ef65fa5E"] chainId_in: [1] } ) { items { txHash timestamp type shares assets vault { address name } } } } ``` ### All Market Positions [#all-market-positions] ```bash curl -sS "https://api.morpho.org/v0/blue/markets/1:0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115/users/0xabcdefabcdefabcdefabcdefabcdefabcdefabcd/position" \ -H "accept: application/json" ``` REST returns one user's position in a market (`collateral_assets`, `supply_shares`, `borrow_shares`). Listing every position in a market, and USD-denominated values, are GraphQL-only. ```graphql query { marketPositions( first: 10 orderBy: SupplyShares orderDirection: Desc where: { marketUniqueKey_in: [ "0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115" ] } ) { items { market { marketId loanAsset { address symbol } collateralAsset { address symbol } } user { address } state { supplyShares supplyAssets supplyAssetsUsd borrowShares borrowAssets borrowAssetsUsd collateral collateralUsd } } } } ``` ## Risk & Oracle Data [#risk--oracle-data] ### Oracle Data [#oracle-data] Oracle data is returned if the oracle's type is `ChainlinkOracle` or `ChainlinkOracleV2`. `creationEvent` field is specific to `ChainlinkOracleV2` oracles. More on oracles and `ChainlinkOracleV2` oracles in the oracle section [here](/learn/concepts/oracle/#morphochainlinkoraclev2-a-reference-implementation). #### All Markets [#all-markets] ```bash curl -sS "https://api.morpho.org/v0/oracles/1:0x2a01EB9496094dA03c4E364Def50f5aD1280AD72/state" \ -H "accept: application/json" ``` REST exposes the oracle's live quote — `price`, `last_updated_at`, `last_indexed_block` — by `:`. The feed composition (`baseFeed*`, `quoteFeed*`, `scaleFactor`, vault conversions) is GraphQL-only. ```graphql query Markets($first: Int, $skip: Int, $orderBy: MarketOrderBy, $orderDirection: OrderDirection, $where: MarketFilters) { markets(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) { items { marketId oracle { address type data { ... on MorphoChainlinkOracleData { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } quoteFeedOne { address } quoteFeedTwo { address } scaleFactor vaultConversionSample } ... on MorphoChainlinkOracleV2Data { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } baseVaultConversionSample quoteFeedOne { address } quoteFeedTwo { address } quoteOracleVault { address } quoteVaultConversionSample scaleFactor } } creationEvent { txHash timestamp blockNumber } } } } } ``` With the following `variables` as example: ```json { "first": 100, "skip": 0, "orderBy": "SupplyAssetsUsd", "orderDirection": "Desc", "where": { "chainId_in": [ 1, 8453 ], "listed": true } } ``` #### Specific Market [#specific-market] ```bash curl -sS "https://api.morpho.org/v0/oracles/1:0x2a01EB9496094dA03c4E364Def50f5aD1280AD72/state" \ -H "accept: application/json" ``` REST exposes the oracle's live quote (`price`, `last_updated_at`, `last_indexed_block`) by `:`; the feed composition is GraphQL-only. ```graphql query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { marketId oracle { address type creationEvent { txHash timestamp blockNumber } data { ... on MorphoChainlinkOracleV2Data { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } baseVaultConversionSample quoteFeedOne { address } quoteFeedTwo { address } quoteOracleVault { address } quoteVaultConversionSample scaleFactor } ... on MorphoChainlinkOracleData { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } quoteFeedOne { address } quoteFeedTwo { address } scaleFactor vaultConversionSample } } } } } ``` ## Historical Data [#historical-data] Historical data is available through both REST and GraphQL, with different parameters for each: * **REST** exposes trailing history via fixed `lookback` windows (`one_day`, `seven_days`, `thirty_days`, `ninety_days`, `one_year`, `inception`) passed as a query string on the relevant `/history` endpoint. It is per-market/per-vault; enumeration and USD-denominated series are GraphQL-only. * **GraphQL** exposes historical data through the `historicalState` field, which takes `startTimestamp`, `endTimestamp`, and `interval` (`YEAR`, `QUARTER`, `MONTH`, `WEEK`, `DAY`, `HOUR`) as query variables — see the GraphQL tab in each example below. The queries need to be backfilled to return proper data (i.e. the historical data needs to be indexed and stored). Some queries are not backfilled and are flagged as deprecated in [the Morpho API sandbox](https://api.morpho.org/graphql).
How to use the GraphQL `historicalState` variables Here are the available variables when using a `historicalState` query: * `startTimestamp`: beginning of the historical data in UNIX timestamp format, * `endTimestamp`: end of the historical data in UNIX timestamp format, * `interval`: interval of the historical data points (`YEAR`, `QUARTER`, `MONTH`, `WEEK`, `DAY`, `HOUR`). Inputting these variables is not mandatory but it is advised to specify them to control the specific data returned. If no variables are specified, the default values will be: * `startTimestamp`: 0, * `endTimestamp`: infinity, * `interval`: will adjust according to `startTimestamp` and `endTimestamp` to return around 50 data points. Note that the `historicalState` field is not accessible through the (multiple) markets(...) and vaults(...) list queries. Historical data is only available through individual market or vault queries like `market(id: "")`, `marketById`, `vault(id: "")` or `vaultByAddress`.
### Historical APYs [#historical-apys] ```bash curl -sS "https://api.morpho.org/v0/blue/markets/8453:0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836/apy/history?lookback=thirty_days" \ -H "accept: application/json" ``` REST returns trailing supply/borrow APY history via fixed `lookback` windows (`one_day`, `seven_days`, `thirty_days`, `ninety_days`, `one_year`, `inception`) instead of an arbitrary `startTimestamp`/`endTimestamp`/`interval`. ```graphql query MarketById($marketId: String!, $chainId: Int!, $options: TimeseriesOptions) { marketById(marketId: $marketId, chainId: $chainId) { historicalState { borrowApy(options: $options) { x y } supplyApy(options: $options) { x y } } } } ``` With the following `variables`: ```json { "marketId": "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836", "chainId": 8453, "options": { "startTimestamp": 1755420554, "endTimestamp": 1755506954, "interval": "HOUR" } } ``` ### Historical Market States [#historical-market-states] ```bash curl -sS "https://api.morpho.org/v0/blue/markets/1:0x608929d6de2a10bacf1046ff157ae38df5b9f466fb89413211efb8f63c63833a/state/history?lookback=thirty_days" \ -H "accept: application/json" ``` `/state/history` returns trailing `total_supply_assets` / `total_borrow_assets` history via fixed `lookback` windows (`one_day` … `inception`). USD-denominated series are GraphQL-only. ```graphql query MarketApys($options: TimeseriesOptions) { marketById( marketId: "0x608929d6de2a10bacf1046ff157ae38df5b9f466fb89413211efb8f63c63833a" chainId: 1 ) { marketId historicalState { supplyAssetsUsd(options: $options) { x y } borrowAssetsUsd(options: $options) { x y } } } } ``` With the following `variables`: ```json "variables": { "startTimestamp": 1707749700, "endTimestamp": 1708354500, "interval": HOUR, } ``` ### Historical Asset Price [#historical-asset-price] ```bash curl -sS "https://api.morpho.org/v0/tokens/1:0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0/price" \ -H "accept: application/json" ``` REST returns the current USD spot price (`price`, `timestamp`, `provider`); a historical price series is GraphQL-only. ```graphql query { wstETHWeeklyPriceUsd: assetByAddress( address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" chainId: 1 ) { historicalPriceUsd( options: { startTimestamp: 1707749700 endTimestamp: 1708354500 interval: HOUR } ) { x y } } } ``` All queries sent for `assetByAddress`, `marketById` and `vaultByAddress` should include the `chainId` parameter. --- # Rewards Source: https://docs.morpho.org/developers/api/rewards ## Endpoints [#endpoints] There are several endpoints to get information about rewards: 1. Morpho API: [https://api.morpho.org/graphql](https://api.morpho.org/graphql) 2. Merkl API: [https://api.merkl.xyz/docs](https://api.merkl.xyz/docs) (external) You can read more about Rewards integration in your product in the dedicated section [here](/developers/rewards/get-started). For Merkl API usage with user rewards, see [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data). ## Legacy Rewards API (Deprecated) [#legacy-rewards-api-deprecated] **Legacy Rewards API Deprecated** The `rewards.morpho.org` API has been deprecated. If you or your users have unclaimed rewards from historical Morpho programs (pre-Merkl migration), they can be claimed directly through the legacy rewards lookup app: 👉 **[rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/)** All new reward programs are distributed exclusively via [Merkl](https://app.merkl.xyz/protocols/morpho?tokenType=all\&sort=tvl-desc). See the [Merkl documentation](https://docs.merkl.xyz/) for integration details. ## Morpho Vault V2 [#morpho-vault-v2] For rewards data, the Morpho API retrieves information from the Merkl API. ```graphql query VaultV2Rewards($address: String!, $chainId: Int!) { vaultV2ByAddress(address: $address, chainId: $chainId) { address rewards { supplyApr asset { address symbol price { usd } } } } } ``` **Variables example:** ```json { "address": "0xb576765fB15505433aF24FEe2c0325895C559FB2", "chainId": 1 } ``` Morpho Vault V2 rewards are pre-aggregated by the API. The `rewards` field already includes all reward sources across all adapters. See [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data#fetching-morpho-vault-v2-rewards) for a complete walkthrough. ## Morpho Vault V1 [#morpho-vault-v1] ### Rewards received through Vault programs [#rewards-received-through-vault-programs] ```graphql { vaults(where: { chainId_in: [8453] }) { items { state { allRewards { asset { address chain { id } } supplyApr } } } } } ``` ### Rewards per allocated market [#rewards-per-allocated-market] ```graphql { marketPositions( where: { userAddress_in: ["0xEbFA750279dEfa89b8D99bdd145a016F6292757b"] } ) { items { supplyAssets market { marketId loanAsset { name symbol } collateralAsset { name symbol } lltv oracle { address } irmAddress state { rewards { asset { address chain { id } } supplyApr } } } } } } ``` ### Complete Vault Rewards [#complete-vault-rewards] To retrieve the rewards that a Morpho Vault V1 inherits, you have to check both the vault-level rewards and the rewards inherited from the allocated markets: ```graphql query VaultByAddress($address: String!, $chainId: Int!) { vaultByAddress(address: $address, chainId: $chainId) { address state { allRewards { asset { address name chain { id } } supplyApr } allocation { market { marketId state { rewards { supplyApr asset { address chain { id } } } } } supplyAssetsUsd } } chain { id } } } ``` **Variables example:** ```json { "address": "0x9aB2d181E4b87ba57D5eD564D3eF652C4E710707", "chainId": 8453 } ``` You can then compute the overall Morpho Vault V1 rewards following [the Morpho Vault rewards computation](/developers/earn/tutorials/get-data#apy-native--rewards). ## Morpho Markets [#morpho-markets] ```graphql { markets(orderBy: SupplyAssetsUsd, orderDirection: Desc) { items { marketId state { rewards { supplyApr borrowApr asset { address chain { id } } } } } } } ```

Explanation of Fields

* `supplyApr`: The annual percentage rate for supplying tokens, available if the reward token is priced. * `borrowApr`: The annual percentage rate for borrowing tokens, available if the reward token is priced.
For any questions on how to integrate the data, feel free to message Morpho via the chatbox on **[help.morpho.org](https://help.morpho.org)**. --- # Morpho Variable Rate Markets for Borrow products Source: https://docs.morpho.org/developers/borrow/get-started Integrating Morpho Variable Rate Markets (Borrow) into your application means implementing the infrastructure that allows users to supply collateral and borrow assets directly from isolated lending markets. Users will pay a borrow rate on their borrowed assets and may receive incentives (rewards). A critical part of a borrow integration is facilitating the monitoring of position health to help users avoid **liquidation**, where their collateral can be seized to repay their debt. Note: Borrowers will see interests accruing in their debt position, while the collateral remains idle, and do not yield. ## Key Components of Borrow Integration [#key-components-of-borrow-integration] When building borrow products with Morpho Markets, you need to integrate three main components: 1. **Market Operations**: Supplying collateral, borrowing, repaying, and withdrawing collateral. 2. **Position Health Management**: Real-time tracking of Loan-to-Value (LTV), Health Factor, and liquidation risk. 3. **Rewards Integration**: Discovery and claiming of incentives for supplying collateral or borrowing. Integrating Borrow gives users access to: 1. **Isolated Risk**: Each market's risk is contained. A problem in one market (e.g., a volatile collateral asset) does not affect the solvency of any other market. 2. **Capital Efficiency**: The underlying interest rate models are designed for high utilization, which translates to better rates for both borrowers and lenders. 3. **Deep Liquidity via Public Allocator**: While markets are isolated, the [Vault V2 Public Allocator](/developers/borrow/tutorials/public-allocator/) enables liquidity to flow between them on demand, ensuring borrowers can access deep liquidity from a single market. See the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). ## Technical Components [#technical-components] From a technical standpoint, integrating Borrow involves: 1. **Market Discovery and Display**: Implementing systems to discover available markets and display their key parameters (Collateral/Loan Asset, APYs, TVL, LLTV, Oracle, IRM). 2. **Transaction Handling**: Building the interface and backend support, including asset approvals, transaction monitoring, and position updates. Here are the four core market operations: * `supplyCollateral` * `borrow` * `repay` * `withdrawCollateral` 3. **Position Management**: Creating interfaces for users to: * View their current collateral and debt balances. * Clearly track their **Health Factor** and current LTV. * Perform any of the four core market operations. * Claim any available rewards. 4. **Liquidation Risk Transparency**: Building clear and prominent displays that communicate: * The user's current Health Factor. * The market's Liquidation Loan-to-Value (LLTV), which is the LTV at which liquidation can occur. * Visual warnings when a user's position health is declining. ## Rewards Considerations [#rewards-considerations] A complete Borrow integration should include rewards functionality. Many markets offer incentives for both borrowers and collateral suppliers through reward campaigns. Key aspects to integrate: * **Rewards Discovery**: Identify available reward programs for borrowers and collateral suppliers * **Rewards Tracking**: Display accruing and claimable rewards in real-time * **Claiming Process**: Implement claim functionality using Merkle-proof based distribution via Merkl * **Multi-Token Support**: Handle various reward tokens from different incentive programs * **Program Awareness**: Display active campaigns and their respective parameters For detailed implementation guidance, see the [Rewards integration guide](/developers/rewards/get-started). ## Assets Flow - Borrow [#assets-flow---borrow] Understanding the complete flow of assets is crucial for a proper integration: ### 1. Supply Collateral [#1-supply-collateral] * A user selects a market (e.g., wstETH/WETH). * They supply a collateral asset (wstETH) to the market. This collateral secures their future debt. ### 2. Borrow [#2-borrow] * The user borrows the loan asset (WETH) from the same market. * The amount they can borrow is limited by the value of their collateral and the market's Liquidation Loan-to-Value (LLTV). * The user's **Health Factor** is established, representing their position's safety from liquidation. ### 3. Repay [#3-repay] * The user repays the loan asset (WETH), either partially or in full. * Repaying debt reduces their LTV and increases their Health Factor, making their position safer. * Interest continuously accrues on the outstanding debt, which must also be repaid. ### 4. Withdraw Collateral [#4-withdraw-collateral] * After repaying debt, the user can withdraw their collateral. * They can only withdraw collateral if their position remains healthy (i.e., their Health Factor stays above 1). If the user has fully repaid their debt, they can withdraw all of their collateral. ## Functional Integration Requirements [#functional-integration-requirements] For a complete Borrow integration, your system needs to implement: 1. **Read Operations**: * Query market parameters (LLTV, oracle, irm, etc.). * Fetch real-time market state (APYs, total supply/borrow, utilization). * Display user-specific position data (collateral, debt, **Health Factor**). * Present available liquidity, including reallocatable liquidity from the Public Allocator; use the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/) to integrate it. 2. **Write Operations**: * Handle token approvals for supplying collateral and repaying debt. * Execute `supplyCollateral`, `borrow`, `repay`, and `withdrawCollateral`. * Facilitate claiming any associated rewards. 3. **Monitoring Capabilities**: * Track position Health Factor changes in real-time. * Alert users when their position approaches the liquidation threshold. * Update market data and interest rates dynamically. ## Risk Considerations [#risk-considerations] A responsible Borrow integration must clearly communicate the following risks to the user: 1. **Liquidation Risk**: This is the primary risk for borrowers. If a position's Health Factor drops below 1, their collateral can be seized by liquidators, resulting in a loss of assets. 2. **Oracle Risk**: Inaccurate or manipulated price feeds from the market's oracle can lead to premature liquidations or prevent timely ones, causing bad debt. 3. **Smart Contract Risk**: Exposure to vulnerabilities in the Morpho core contracts. 4. **Market Liquidity Risk**: Low liquidity in a market can prevent users from borrowing or impact interest rates significantly. 5. **Counterparty Risk**: The risk associated with the assets themselves (e.g., de-pegging of a stablecoin or centralization risk of a token). By properly integrating these components and transparently communicating risks, your application can offer users direct access to Morpho's efficient and secure lending markets. ## UX Requirements [#ux-requirements] When building a Borrow product on Morpho, you must follow these UX requirements to help users understand what they're interacting with and ensure a transparent experience. ### Attribution [#attribution] You must include a "Powered by Morpho" mention in your interface, for example in the footer or near the borrow product section. This helps users identify the underlying protocol and builds trust in the infrastructure behind your product. See [Brand Assets](https://brand.morpho.org/) for official logos and usage guidelines. ### User Disclaimer [#user-disclaimer] You must display the following notice to end users at least the first time they interact with the protocol through your app, ideally with a checkbox confirmation before they proceed. Replace **\[Your App]** with your application's name before displaying this notice: > Accessing the Morpho Protocol through this app is governed by **\[Your App]**'s Terms of Use and [Morpho's Disclaimer](https://morpho.org/disclaimers/). By using it, you acknowledge that you have read and understood these terms and the risks involved. --- # Addresses Source: https://docs.morpho.org/developers/contracts/addresses Morpho smart contracts are deployed and optimized specifically for Cancun EVM version. Any subsequent protocol upgrades or hard forks by the network operators are beyond the control of the Morpho Association. The Morpho Association assumes no responsibility for contract malfunctions, permanent loss of assets, or failed logic resulting from the changes or removal of critical opcodes or fundamental changes to the EVM execution environment. ## Morpho Midnight [#morpho-midnight] | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------- | | Midnight | [ 0x471686c42792F93528B000beF54bC10E3aa2045f ](https://etherscan.io/address/0x471686c42792F93528B000beF54bC10E3aa2045f#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/Midnight.sol) | | SetterRatifier | [ 0xb72c416382c8A6399D0765CebfB032F040B00B3c ](https://etherscan.io/address/0xb72c416382c8A6399D0765CebfB032F040B00B3c#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/ratifiers/SetterRatifier.sol) | | EcrecoverRatifier | [ 0xAC439c81CAA6ef4C7B7E8F0110F8CE63A4b6D43e ](https://etherscan.io/address/0xAC439c81CAA6ef4C7B7E8F0110F8CE63A4b6D43e#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/ratifiers/EcrecoverRatifier.sol) | | EcrecoverAuthorizer | [ 0xfC3303119E46AF831CacdBDB6e1A04C9C369ffF7 ](https://etherscan.io/address/0xfC3303119E46AF831CacdBDB6e1A04C9C369ffF7#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/EcrecoverAuthorizer.sol) | | Mempool | [ 0xde2d62449301a09A51EbF9326EA60d2e8BF4A8F7 ](https://etherscan.io/address/0xde2d62449301a09A51EbF9326EA60d2e8BF4A8F7#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/log/Log.sol) | | BlueBuyCallback Factory | [ 0x172d1FdC5f79bFe1ED46448f18541E591E5c93a7 ](https://etherscan.io/address/0x172d1FdC5f79bFe1ED46448f18541E591E5c93a7#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/blue-buy-callback/BlueBuyCallbackFactory.sol) | | BlueFallbackRolling | [ 0x6910E21c20a09BB3a2026a24585aF1E63d769fe6 ](https://etherscan.io/address/0x6910E21c20a09BB3a2026a24585aF1E63d769fe6#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/blue-fallback-rolling/BlueFallbackRolling.sol) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------- | | Midnight | [ 0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A ](https://basescan.org/address/0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/Midnight.sol) | | SetterRatifier | [ 0x800B5F12A61B8198a5a6EfD794Cac6699B294d63 ](https://basescan.org/address/0x800B5F12A61B8198a5a6EfD794Cac6699B294d63#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/ratifiers/SetterRatifier.sol) | | EcrecoverRatifier | [ 0xd6e70365C8E8DDa9a4ca662C07bbE663b017755E ](https://basescan.org/address/0xd6e70365C8E8DDa9a4ca662C07bbE663b017755E#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/ratifiers/EcrecoverRatifier.sol) | | EcrecoverAuthorizer | [ 0x292bEa9f1443d54E0E509120c919106765c6a493 ](https://basescan.org/address/0x292bEa9f1443d54E0E509120c919106765c6a493#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/EcrecoverAuthorizer.sol) | | Mempool | [ 0xdD6DCE32e21f7b020898a8258dA37355b4017993 ](https://basescan.org/address/0xdD6DCE32e21f7b020898a8258dA37355b4017993#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/log/Log.sol) | | BlueBuyCallback Factory | [ 0x7337f119Eca028bD39E0e543cEf71631D2333425 ](https://basescan.org/address/0x7337f119Eca028bD39E0e543cEf71631D2333425#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/blue-buy-callback/BlueBuyCallbackFactory.sol) | | BlueFallbackRolling | [ 0x7bfA8207B818cc0e23a7aBdCBe56572fAbCa831F ](https://basescan.org/address/0x7bfA8207B818cc0e23a7aBdCBe56572fAbCa831F#code) | [morpho-org/midnight](https://github.com/morpho-org/midnight/blob/main/src/periphery/blue-fallback-rolling/BlueFallbackRolling.sol) | ## Morpho Blue [#morpho-blue] | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------- | | Morpho | [ 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb ](https://etherscan.io/address/0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC ](https://etherscan.io/address/0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766 ](https://etherscan.io/address/0x3A7bB36Ee3f3eE32A60e9f2b33c1e5f2E83ad766#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xc85CE8ffdA27b646D269516B8d0Fa6ec2E958B55 ](https://abscan.org/address/0xc85CE8ffdA27b646D269516B8d0Fa6ec2E958B55#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xd334eb112CfD1EB4a50FB871b7D9895EBB955C43 ](https://abscan.org/address/0xd334eb112CfD1EB4a50FB871b7D9895EBB955C43#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x3585E3fD72F8d1b02250E1F6496b706c6e092884 ](https://abscan.org/address/0x3585E3fD72F8d1b02250E1F6496b706c6e092884#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x6c247b1F6182318877311737BaC0844bAa518F5e ](https://arbiscan.io/address/0x6c247b1F6182318877311737BaC0844bAa518F5e) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x66F30587FB8D4206918deb78ecA7d5eBbafD06DA ](https://arbiscan.io/address/0x66F30587FB8D4206918deb78ecA7d5eBbafD06DA) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x98Ce5D183DC0c176f54D37162F87e7eD7f2E41b5 ](https://arbiscan.io/address/0x98Ce5D183DC0c176f54D37162F87e7eD7f2E41b5) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | 0x34CD04070dD72b14E241112F6d83812Df5Af7fCD | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | 0xF02615d094Fc02fC031C35fe705e175aA4653f20 | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | 0xbFc8D6167a02889D5EB08a023d9aB712B44f7dE8 | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x895383274303AA19fe978AFB4Ac55C7f094f982C ](https://snowtrace.io/address/0x895383274303AA19fe978AFB4Ac55C7f094f982C/contract/43114/code?chainid=43114) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xb6ac9477D574EE2a7BF32d2475b303fb70968AA4 ](https://snowtrace.io/address/0xb6ac9477D574EE2a7BF32d2475b303fb70968AA4/contract/43114/code?chainid=43114) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xF0c1299D44b3803243d7c1eEC2042e9484Db13f2 ](https://snowtrace.io/address/0xF0c1299D44b3803243d7c1eEC2042e9484Db13f2/contract/43114/code?chainid=43114) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb ](https://basescan.org/address/0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x46415998764C29aB2a25CbeA6254146D50D22687 ](https://basescan.org/address/0x46415998764C29aB2a25CbeA6254146D50D22687#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x2DC205F24BCb6B311E5cdf0745B0741648Aebd3d ](https://basescan.org/address/0x2DC205F24BCb6B311E5cdf0745B0741648Aebd3d#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xAeA7eFF1bD3c875c18ef50F0387892dF181431C6 ](https://www.btrscan.com/address/0xaea7eff1bd3c875c18ef50f0387892df181431c6?tab=Contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xefB565442B9Eb740B50Cf928C14d21c0111254F9 ](https://www.btrscan.com/address/0xefB565442B9Eb740B50Cf928C14d21c0111254F9?tab=Contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xfDc69d06De855701731D142F28bD401802DA4daF ](https://www.btrscan.com/address/0xfDc69d06De855701731D142F28bD401802DA4daF?tab=Contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x01b0Bd309AA75547f7a37Ad7B1219A898E67a83a ](https://bscscan.com/address/0x01b0Bd309AA75547f7a37Ad7B1219A898E67a83a#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x7112D95cB5f6b13bF5F5B94a373bB3b2B381F979 ](https://bscscan.com/address/0x7112D95cB5f6b13bF5F5B94a373bB3b2B381F979#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xDf2035fC15919588526dBb5560863C812F135236 ](https://bscscan.com/address/0xDf2035fC15919588526dBb5560863C812F135236#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xea4f2979D7A99B40404b447Cf71c008e3805760F ](https://camp.cloud.blockscout.com/address/0xea4f2979D7A99B40404b447Cf71c008e3805760F?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xd5661D965cc60ed1954d4f6725b766051De3ef97 ](https://camp.cloud.blockscout.com/address/0xd5661D965cc60ed1954d4f6725b766051De3ef97?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x391A3fd481743FE48409e2e31eDac8a5f4C7653A ](https://camp.cloud.blockscout.com/address/0x391A3fd481743FE48409e2e31eDac8a5f4C7653A?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xd24ECdD8C1e0E57a4E26B1a7bbeAa3e95466A569 ](https://celoscan.io/address/0xd24ECdD8C1e0E57a4E26B1a7bbeAa3e95466A569#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x683CAAADdfA2F42e24880E202676526d501a5dED ](https://celoscan.io/address/0x683CAAADdfA2F42e24880E202676526d501a5dED#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x3a4849b5174Dc6828c6Dc9BBD87e61Ed1ebE9fFA ](https://celoscan.io/address/0x3a4849b5174Dc6828c6Dc9BBD87e61Ed1ebE9fFA#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x99D31FEcc885204b4136ea5D2ef2a37F36E3AeB8 ](https://explorer.mainnet.citrea.xyz/address/0x99D31FEcc885204b4136ea5D2ef2a37F36E3AeB8?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x723E35C4CBb5B91669b62F130f2aC12931DF8049 ](https://explorer.mainnet.citrea.xyz/address/0x723E35C4CBb5B91669b62F130f2aC12931DF8049?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xFd32e705E2bc6e716e2522Df0391BF26DfbADBAB ](https://explorer.mainnet.citrea.xyz/address/0xFd32e705E2bc6e716e2522Df0391BF26DfbADBAB?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xDF9a1DC07e5dEe5ccCCaBeC35e446C70fAF7434e ](https://explorer.cronos.org/address/0xDF9a1DC07e5dEe5ccCCaBeC35e446C70fAF7434e#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x1Db002C086439d55B9f33E6c0693Eb850F7c0607 ](https://explorer.cronos.org/address/0x1Db002C086439d55B9f33E6c0693Eb850F7c0607#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x44b2c8e4474751EF2BBC57B92928aFB99DA785De ](https://explorer.cronos.org/address/0x44b2c8e4474751EF2BBC57B92928aFB99DA785De#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xF050a2BB0468FF23cF2964AC182196C94D6815C3 ](https://eden.blockscout.com/address/0xF050a2BB0468FF23cF2964AC182196C94D6815C3?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x08A7b3a39E5425d616Cc9c046cf96B5eF21a139f ](https://eden.blockscout.com/address/0x08A7b3a39E5425d616Cc9c046cf96B5eF21a139f?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xD6202eFF2e869dc473EB13c38Cc787835Bf8B6df ](https://eden.blockscout.com/address/0xD6202eFF2e869dc473EB13c38Cc787835Bf8B6df?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xbCE7364E63C3B13C73E9977a83c9704E2aCa876e ](https://explorer.etherlink.com/address/0xbCE7364E63C3B13C73E9977a83c9704E2aCa876e?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xC1523BE776e66ba07b609b1914D0925278f21FE5 ](https://explorer.etherlink.com/address/0xC1523BE776e66ba07b609b1914D0925278f21FE5?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x12FA40f687a35611720E1DcB59976B6e51247298 ](https://explorer.etherlink.com/address/0x12FA40f687a35611720E1DcB59976B6e51247298?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xF4346F5132e810f80a28487a79c7559d9797E8B0 ](https://flare-explorer.flare.network/address/0xF4346F5132e810f80a28487a79c7559d9797E8B0?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xE5B5627C5973AfAE1928a6b8e5c1D6AABFEC8a7a ](https://flare-explorer.flare.network/address/0xE5B5627C5973AfAE1928a6b8e5c1D6AABFEC8a7a?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x95cB3625598F9abf6cb8B874AA1EfEEbE7822642 ](https://flare-explorer.flare.network/address/0x95cB3625598F9abf6cb8B874AA1EfEEbE7822642?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xa6030627d724bA78a59aCf43Be7550b4C5a0653b ](https://fraxscan.com/address/0xa6030627d724bA78a59aCf43Be7550b4C5a0653b) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xA0D4D77b5D9933073572E19C172BFE866312673b ](https://fraxscan.com/address/0xA0D4D77b5D9933073572E19C172BFE866312673b) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x39d8622C607A691D7705E8842fbB12E3c38dCD41 ](https://fraxscan.com/address/0x39d8622C607A691D7705E8842fbB12E3c38dCD41) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x8c45B34999883FF4B47cD3be095D585682cd9227 ](https://gensyn-mainnet.explorer.alchemy.com/address/0x8c45B34999883FF4B47cD3be095D585682cd9227?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x549EFFAE58F9Db253AAF60fbCeC8B4cB74a952A8 ](https://gensyn-mainnet.explorer.alchemy.com/address/0x549EFFAE58F9Db253AAF60fbCeC8B4cB74a952A8?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xf9b22d1652ce918CfC5d102269801AFbfEFa85F9 ](https://gensyn-mainnet.explorer.alchemy.com/address/0xf9b22d1652ce918CfC5d102269801AFbfEFa85F9?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xB74D4dd451E250bC325AFF0556D717e4E2351c66 ](https://gnosis.blockscout.com/address/0xB74D4dd451E250bC325AFF0556D717e4E2351c66?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xae529333703C34b8976BaB9D04AF3f0B9Cff05c5 ](https://gnosis.blockscout.com/address/0xae529333703C34b8976BaB9D04AF3f0B9Cff05c5?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x8FFd9e3A4B02D217C3EBB5e48E64f748d7476c32 ](https://gnosis.blockscout.com/address/0x8FFd9e3A4B02D217C3EBB5e48E64f748d7476c32?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xa4Ca2c2e25b97DA19879201bA49422bc6f181f42 ](https://explorer.hemi.xyz/address/0xa4Ca2c2e25b97DA19879201bA49422bc6f181f42) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xdEbdEa31624552DF904A065221cD14088ABDeD70 ](https://explorer.hemi.xyz/address/0xdEbdEa31624552DF904A065221cD14088ABDeD70) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xB3cb32E6185446a6Bc7A047E4FfA138fA939e133 ](https://explorer.hemi.xyz/address/0xB3cb32E6185446a6Bc7A047E4FfA138fA939e133) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0x68e37dE8d93d3496ae143F2E900490f6280C57cD ](https://hyperevmscan.io/address/0x68e37dE8d93d3496ae143F2E900490f6280C57cD#code) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xD4a426F010986dCad727e8dd6eed44cA4A9b7483 ](https://hyperevmscan.io/address/0xD4a426F010986dCad727e8dd6eed44cA4A9b7483#code) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0xeb476f124FaD625178759d13557A72394A6f9aF5 ](https://hyperevmscan.io/address/0xeb476f124FaD625178759d13557A72394A6f9aF5#code) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x857f3EefE8cbda3Bc49367C996cd664A880d3042 ](https://explorer.inkonchain.com/address/0x857f3EefE8cbda3Bc49367C996cd664A880d3042) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x9515407b1512F53388ffE699524100e7270Ee57B ](https://explorer.inkonchain.com/address/0x9515407b1512F53388ffE699524100e7270Ee57B) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x3FFFE273ee348b9E1ef89533025C7f165B17B439 ](https://explorer.inkonchain.com/address/0x3FFFE273ee348b9E1ef89533025C7f165B17B439) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xA8BEebdca34d83C697c302A0594f3c41f3994cd2 ](https://kaiascan.io/address/0xA8BEebdca34d83C697c302A0594f3c41f3994cd2?tabId=contract\&page=1) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xA4E2bA20Fc64D721D95BD5a28FF71844C5bb5cF2 ](https://kaiascan.io/address/0xA4E2bA20Fc64D721D95BD5a28FF71844C5bb5cF2?tabId=contract\&page=1) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x3e89C1071814b2c4170c90260Fcb60B903AD4602 ](https://kaiascan.io/address/0x3e89C1071814b2c4170c90260Fcb60B903AD4602?tabId=contract\&page=1) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xD50F2DffFd62f94Ee4AEd9ca05C61d0753268aBc ](https://katanascan.com/address/0xD50F2DffFd62f94Ee4AEd9ca05C61d0753268aBc#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x4F708C0ae7deD3d74736594C2109C2E3c065B428 ](https://katanascan.com/address/0x4F708C0ae7deD3d74736594C2109C2E3c065B428#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x7D047fB910Bc187C18C81a69E30Fa164f8c536eC ](https://katanascan.com/address/0x7D047fB910Bc187C18C81a69E30Fa164f8c536eC#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x6B0D716aC0A45536172308e08fC2C40387262c9F ](https://lineascan.build/address/0x6B0D716aC0A45536172308e08fC2C40387262c9F#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x85C2Ef4Bd69f42D7Da19Fb9dcdD7Fb8d0F59cDeE ](https://lineascan.build/address/0x85C2Ef4Bd69f42D7Da19Fb9dcdD7Fb8d0F59cDeE#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x3FFF726062B03BfD5BC485eeEEcc92CF1d8F0105 ](https://lineascan.build/address/0x3FFF726062B03BfD5BC485eeEEcc92CF1d8F0105#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0x00cD58DEEbd7A2F1C55dAec715faF8aed5b27BF8 ](https://blockscout.lisk.com/address/0x00cD58DEEbd7A2F1C55dAec715faF8aed5b27BF8?tab=contract) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x5576629f21D528A8c3e06C338dDa907B94563902 ](https://blockscout.lisk.com/address/0x5576629f21D528A8c3e06C338dDa907B94563902?tab=contract) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0x2eb4D17C2AAf1EA62Bf83Fb49Dd1128b14AF4D93 ](https://blockscout.lisk.com/address/0x2eb4D17C2AAf1EA62Bf83Fb49Dd1128b14AF4D93?tab=contract) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x18120312A7cf44DcfEc6dCe5632a431579ED9100 ](https://megaeth.blockscout.com/address/0x18120312A7cf44DcfEc6dCe5632a431579ED9100?tab=contract_code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x56875764185548B0ca72A1877b3aE15E44e8A323 ](https://megaeth.blockscout.com/address/0x56875764185548B0ca72A1877b3aE15E44e8A323?tab=contract_code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x02522D475E7064E1d1E966e3197db050b19b2FC2 ](https://megaeth.blockscout.com/address/0x02522D475E7064E1d1E966e3197db050b19b2FC2?tab=contract_code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xd85cE6BD68487E0AaFb0858FDE1Cd18c76840564 ](https://explorer.mode.network/address/0xd85cE6BD68487E0AaFb0858FDE1Cd18c76840564) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xE3d46Ae190Cb39ccA3655E966DcEF96b4eAe1d1c ](https://explorer.mode.network/address/0xE3d46Ae190Cb39ccA3655E966DcEF96b4eAe1d1c) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xf9380f7898423Bd7FDe3C9fDD1b2671A2471f39D ](https://explorer.mode.network/address/0xf9380f7898423Bd7FDe3C9fDD1b2671A2471f39D) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [0xD5D960E8C380B724a48AC59E2DfF1b2CB4a1eAee](https://monadscan.com/address/0xd5d960e8c380b724a48ac59e2dff1b2cb4a1eaee#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [0x09475a3D6eA8c314c592b1a3799bDE044E2F400F](https://monadscan.com/address/0x09475a3D6eA8c314c592b1a3799bDE044E2F400F#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [0xC8659Bcd5279DB664Be973aEFd752a5326653739](https://monadscan.com/address/0xC8659Bcd5279DB664Be973aEFd752a5326653739#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xAd10d07901Dc3195c3cb5e78E061F4EA8D9B4905 ](https://explorer.morph.network/address/0xAd10d07901Dc3195c3cb5e78E061F4EA8D9B4905?tab=contract_code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xfB69467De332E03FF502B85bB2249d2f721F3319 ](https://explorer.morph.network/address/0xfB69467De332E03FF502B85bB2249d2f721F3319?tab=contract_code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xE91032cE5B8D86bA4Be146c186984F49Af37fbf1 ](https://explorer.morph.network/address/0xE91032cE5B8D86bA4Be146c186984F49Af37fbf1?tab=contract_code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xce95AfbB8EA029495c66020883F87aaE8864AF92 ](https://optimistic.etherscan.io/address/0xce95AfbB8EA029495c66020883F87aaE8864AF92) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x8cD70A8F399428456b29546BC5dBe10ab6a06ef6 ](https://optimistic.etherscan.io/address/0x8cD70A8F399428456b29546BC5dBe10ab6a06ef6) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x1ec408D4131686f727F3Fd6245CF85Bc5c9DAD70 ](https://optimistic.etherscan.io/address/0x1ec408D4131686f727F3Fd6245CF85Bc5c9DAD70) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | 0x18573fA18fd17dDfD790B4a5B5b2977aad3b4Efb | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | 0xD5E02889C13230458506CC842347c4E62F8cDF3a | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | 0xb8118256d8Aa950ec0B26a0b8Be7C6c1a858f6a3 | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0x2fF74A46536f5c67ef5A42FD5B4e2Ed8A2cee249 ](https://plasmascan.to/address/0x2fF74A46536f5c67ef5A42FD5B4e2Ed8A2cee249/contract/9745/code) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xf37f73028b5B4ebAdC055Deb24B1cde271CF0f71 ](https://plasmascan.to/address/0xf37f73028b5B4ebAdC055Deb24B1cde271CF0f71/contract/9745/code) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0x5476AeEE80D87649fDD8Ee19eA65976F28525f7a ](https://plasmascan.to/address/0x5476AeEE80D87649fDD8Ee19eA65976F28525f7a/contract/9745/code) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0x42b18785CE0Aed7BF7Ca43a39471ED4C0A3e0bB5 ](https://explorer.plume.org/address/0x42b18785CE0Aed7BF7Ca43a39471ED4C0A3e0bB5?tab=contract) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x7420302Ddd469031Cd2282cd64225cCd46F581eA ](https://explorer.plume.org/address/0x7420302Ddd469031Cd2282cd64225cCd46F581eA?tab=contract) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0x133F742c0D36864F37e15C33a18bA6fdc950ED0f ](https://explorer.plume.org/address/0x133F742c0D36864F37e15C33a18bA6fdc950ED0f?tab=contract) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x1bF0c2541F820E775182832f06c0B7Fc27A25f67 ](https://polygonscan.com/address/0x1bF0c2541F820E775182832f06c0B7Fc27A25f67) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xe675A2161D4a6E2de2eeD70ac98EEBf257FBF0B0 ](https://polygonscan.com/address/0xe675A2161D4a6E2de2eeD70ac98EEBf257FBF0B0) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x1ff7895Eb842794c5d07C4c547b6730e61295215 ](https://polygonscan.com/address/0x1ff7895Eb842794c5d07C4c547b6730e61295215) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x363D2Ba422Db2552752f2EB10b419f0Eb5defD31 ](https://explorer.risechain.com/address/0x363D2Ba422Db2552752f2EB10b419f0Eb5defD31?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x36ef707A1334d35b3056d0088DdA8b82b86948D1 ](https://explorer.risechain.com/address/0x36ef707A1334d35b3056d0088DdA8b82b86948D1?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x8FCcF489b1F9EC73E8B5Aa71432996cD08B3f6C3 ](https://explorer.risechain.com/address/0x8FCcF489b1F9EC73E8B5Aa71432996cD08B3f6C3?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x9D53d5E3bd5E8d4Cbfa6DB1ca238AEA02E651010 ](https://robinhoodchain.blockscout.com/address/0x9D53d5E3bd5E8d4Cbfa6DB1ca238AEA02E651010?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x2BD3d5965B26B51814AC95127B2b80dD6CcC0fa1 ](https://robinhoodchain.blockscout.com/address/0x2BD3d5965B26B51814AC95127B2b80dD6CcC0fa1?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xB7c16F6F8cF531447Bf27Ca7220f981E79C9cdF2 ](https://robinhoodchain.blockscout.com/address/0xB7c16F6F8cF531447Bf27Ca7220f981E79C9cdF2?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x2d012EdbAdc37eDc2BC62791B666f9193FDF5a55 ](https://scrollscan.com/address/0x2d012EdbAdc37eDc2BC62791B666f9193FDF5a55) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xa5EA7500A27C0079961D93366A6e93aafF18CB90 ](https://scrollscan.com/address/0xa5EA7500A27C0079961D93366A6e93aafF18CB90) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xb5961902E60b188b1c665B7b72Ef616656A9e24E ](https://scrollscan.com/address/0xb5961902E60b188b1c665B7b72Ef616656A9e24E) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xc9cDAc20FCeAAF616f7EB0bb6Cd2c69dcfa9094c ](https://seiscan.io/address/0xc9cDAc20FCeAAF616f7EB0bb6Cd2c69dcfa9094c#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x6eFA8e3Aa8279eB2fd46b6083A9E52dA72EA56c4 ](https://seiscan.io/address/0x6eFA8e3Aa8279eB2fd46b6083A9E52dA72EA56c4#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x4bD68c2FF3274207EC07ED281C915758b6F23F07 ](https://seiscan.io/address/0x4bD68c2FF3274207EC07ED281C915758b6F23F07#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0xE75Fc5eA6e74B824954349Ca351eb4e671ADA53a ](https://soneium.blockscout.com/address/0xE75Fc5eA6e74B824954349Ca351eb4e671ADA53a?tab=contract) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x68F9b666b984527A7c145Db4103Cc6d3171C797F ](https://soneium.blockscout.com/address/0x68F9b666b984527A7c145Db4103Cc6d3171C797F?tab=contract) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0x669F1A4cE3127740eCdB3E36adFC5Df6Db1EA74b ](https://soneium.blockscout.com/address/0x669F1A4cE3127740eCdB3E36adFC5Df6Db1EA74b?tab=contract) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xd6c916eB7542D0Ad3f18AEd0FCBD50C582cfa95f ](https://sonicscan.org/address/0xd6c916eB7542D0Ad3f18AEd0FCBD50C582cfa95f) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xDEfCf242226425f93d8DD0e314735C28517C473F ](https://sonicscan.org/address/0xDEfCf242226425f93d8DD0e314735C28517C473F) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x7DA59Fa482F1F49fADc486d8e47BADc506fEb86d ](https://sonicscan.org/address/0x7DA59Fa482F1F49fADc486d8e47BADc506fEb86d) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xa40103088A899514E3fe474cD3cc5bf811b1102e ](https://stablescan.xyz/address/0xa40103088A899514E3fe474cD3cc5bf811b1102e#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x41e846FC8108b8527C1D4EDB4c9564E56442940f ](https://stablescan.xyz/address/0x41e846FC8108b8527C1D4EDB4c9564E56442940f#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xF24C6eAB91e43EacE18a4e893a48565C09132505 ](https://stablescan.xyz/address/0xF24C6eAB91e43EacE18a4e893a48565C09132505#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x918B9F2E4B44E20c6423105BB6cCEB71473aD35c ](https://explorer.tac.build/address/0x918B9F2E4B44E20c6423105BB6cCEB71473aD35c?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x7E82b16496fA8CC04935528dA7F5A2C684A3C7A3 ](https://explorer.tac.build/address/0x7E82b16496fA8CC04935528dA7F5A2C684A3C7A3?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xbf10eD52dD60C60E901BF022c3675303ad4a56b1 ](https://explorer.tac.build/address/0xbf10eD52dD60C60E901BF022c3675303ad4a56b1?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x10EE9AAC980A180dd4DcFc96C746d60B0EA88f97 ](https://explore.tempo.xyz/address/0x10EE9AAC980A180dd4DcFc96C746d60B0EA88f97?tab=contract) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x112fd4042E442C3C12C67AD23587b0afe36eB74E ](https://explore.tempo.xyz/address/0x112fd4042E442C3C12C67AD23587b0afe36eB74E?tab=contract) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xc2c167BC5cBD833ce58239e85073258F10aD4DF6 ](https://explore.tempo.xyz/address/0xc2c167BC5cBD833ce58239e85073258F10aD4DF6?tab=contract) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------- | | Morpho | [ 0x8f5ae9CddB9f68de460C77730b018Ae7E04a140A ](https://uniscan.xyz/address/0x8f5ae9CddB9f68de460C77730b018Ae7E04a140A) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x9a6061d51743B31D2c3Be75D83781Fa423f53F0E ](https://uniscan.xyz/address/0x9a6061d51743B31D2c3Be75D83781Fa423f53F0E) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x43269546e1D586a1f7200a0AC07e26f9631f7539 ](https://uniscan.xyz/address/0x43269546e1D586a1f7200a0AC07e26f9631f7539) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xE741BC7c34758b4caE05062794E8Ae24978AF432 ](https://worldscan.org/address/0xE741BC7c34758b4caE05062794E8Ae24978AF432) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x34E99D604751a72cF8d0CFDf87069292d82De472 ](https://worldscan.org/address/0x34E99D604751a72cF8d0CFDf87069292d82De472) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xd706690BA1Fe26b70c4AD89e60ff62cEB3A2eD02 ](https://worldscan.org/address/0xd706690BA1Fe26b70c4AD89e60ff62cEB3A2eD02) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xEa49B0fE898aF913A3826F9f462eE2cDcb854fD9 ](https://xdcscan.com/address/0xEa49B0fE898aF913A3826F9f462eE2cDcb854fD9#code) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x15c7312B0f26aa0AA70B24a0D2AF87B9e7D614A0 ](https://xdcscan.com/address/0x15c7312B0f26aa0AA70B24a0D2AF87B9e7D614A0#code) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0x6Ad93a3aA829514473D3DF67382894A76c7283B4 ](https://xdcscan.com/address/0x6Ad93a3aA829514473D3DF67382894A76c7283B4#code) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------- | | Morpho | [ 0xA902A365Fe10B4a94339B5A2Dc64F60c1486a5c8 ](https://explorer.zircuit.com/address/0xA902A365Fe10B4a94339B5A2Dc64F60c1486a5c8?activeTab=3) | [morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0xBADb1809ecF658F36e31CcC980F72de029e1cE46 ](https://explorer.zircuit.com/address/0xBADb1809ecF658F36e31CcC980F72de029e1cE46?activeTab=3) | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho ChainlinkOracleV2 Factory | [ 0xf8ddd4745dCB3c00eA5C6211E74A8c7328153cf9 ](https://explorer.zircuit.com/address/0xf8ddd4745dCB3c00eA5C6211E74A8c7328153cf9?activeTab=3) | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/releases/tag/v2.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb ](https://sepolia.basescan.org/address/0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x46415998764C29aB2a25CbeA6254146D50D22687 ](https://sepolia.basescan.org/address/0x46415998764C29aB2a25CbeA6254146D50D22687) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0x2DC205F24BCb6B311E5cdf0745B0741648Aebd3d ](https://sepolia.basescan.org/address/0x2DC205F24BCb6B311E5cdf0745B0741648Aebd3d) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | | Contract | Address | Source Code | | :--------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | | Morpho | [ 0xd011EE229E7459ba1ddd22631eF7bF528d424A14 ](https://sepolia.etherscan.io/address/0xd011EE229E7459ba1ddd22631eF7bF528d424A14) | [morpho-org/morpho-blue](https://github.com/morpho-org/morpho-blue/tree/v1.0.0) | | Adaptive Curve Irm | [ 0x8C5dDCD3F601c91D1BF51c8ec26066010ACAbA7c ](https://sepolia.etherscan.io/address/0x8C5dDCD3F601c91D1BF51c8ec26066010ACAbA7c) | [morpho-org/morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm/tree/v1.0.0) | | Morpho Chainlink Oracle V2 Factory | [ 0xa6c843fc53aAf6EF1d173C4710B26419667bF6CD ](https://sepolia.etherscan.io/address/0xa6c843fc53aAf6EF1d173C4710B26419667bF6CD) | [morpho-org/morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles/tree/v1.0.0) | ## Morpho Vault V2 [#morpho-vault-v2] | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xA1D94F746dEfa1928926b84fB2596c06926C0405 ](https://etherscan.io/address/0xA1D94F746dEfa1928926b84fB2596c06926C0405#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xD1B8E2dee25c2b89DCD2f98448a7ce87d6F63394 ](https://etherscan.io/address/0xD1B8E2dee25c2b89DCD2f98448a7ce87d6F63394#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x32BB1c0D48D8b1B3363e86eeB9A0300BAd61ccc1 ](https://etherscan.io/address/0x32BB1c0D48D8b1B3363e86eeB9A0300BAd61ccc1#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x3696c5eAe4a7Ffd04Ea163564571E9CD8Ed9364e ](https://etherscan.io/address/0x3696c5eAe4a7Ffd04Ea163564571E9CD8Ed9364e#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x00b8e1509398ED692C3F326CbAf1694F9A881e27 ](https://etherscan.io/address/0x00b8e1509398ED692C3F326CbAf1694F9A881e27#code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xecCd168c7d8e40f7166Fe226B4cf2cA3Db7A9754 ](https://abscan.org/address/0xecCd168c7d8e40f7166Fe226B4cf2cA3Db7A9754#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xAA2D848d759d872A45e5658B55B02e589101D9C0 ](https://abscan.org/address/0xAA2D848d759d872A45e5658B55B02e589101D9C0#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x906A0E39C8329b73011d033A3441d2f013013a1A ](https://abscan.org/address/0x906A0E39C8329b73011d033A3441d2f013013a1A#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x6b46fa3cc9EBF8aB230aBAc664E37F2966Bf7971 ](https://arbiscan.io/address/0x6b46fa3cc9EBF8aB230aBAc664E37F2966Bf7971#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xD8Fc8a85779551e78B516da9f74061cb3b086793 ](https://arbiscan.io/address/0xD8Fc8a85779551e78B516da9f74061cb3b086793#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xeF84b1ecEbe43283ec5AF95D7a5c4D7dE0a9859b ](https://arbiscan.io/address/0xeF84b1ecEbe43283ec5AF95D7a5c4D7dE0a9859b#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xc00eb3c7aD1aE986A7f05F5A9d71aCa39c763C65 ](https://arbiscan.io/address/0xc00eb3c7aD1aE986A7f05F5A9d71aCa39c763C65#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x85b66Fe31e6788E5a6825EAe689f4c6c38AF3704 ](https://arbiscan.io/address/0x85b66Fe31e6788E5a6825EAe689f4c6c38AF3704) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | 0x3b0eefaBfa22ec7CF2c73877ac16e78D76749f12 | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | 0x6C2FF5114E45b50bc7195c2F1f87C98cbdad62Cc | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | 0xdEBC92370Cd74d55DA144116138681dbbb528765 | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | MorphoVaultV1 AdapterFactory | 0x77788033B22CEaB8D51Ec8F9dFD4a40E54F380B0 | [vault-v2](https://github.com/morpho-org/vault-v2) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xf7b1d9e43BAeA3705f2B303693766ACbcfec6A55 ](https://snowtrace.io/address/0xf7b1d9e43BAeA3705f2B303693766ACbcfec6A55/contract/43114/code?chainid=43114) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x9633D22Bb8F42f6f70DbbBe34c11EB9209769b8b ](https://snowtrace.io/address/0x9633D22Bb8F42f6f70DbbBe34c11EB9209769b8b/contract/43114/code?chainid=43114) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x66dC122CF454576684Ad78A2800a8Eb052b2E9a6 ](https://snowtrace.io/address/0x66dC122CF454576684Ad78A2800a8Eb052b2E9a6/contract/43114/code?chainid=43114) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x4501125508079A99ebBebCE205DeC9593C2b5857 ](https://basescan.org/address/0x4501125508079A99ebBebCE205DeC9593C2b5857#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xF42D9c36b34c9c2CF3Bc30eD2a52a90eEB604642 ](https://basescan.org/address/0xF42D9c36b34c9c2CF3Bc30eD2a52a90eEB604642#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x9a1B378C43BA535cDB89934230F0D3890c51C0EB ](https://basescan.org/address/0x9a1B378C43BA535cDB89934230F0D3890c51C0EB#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x5C2531Cbd2cf112Cf687da3Cd536708aDd7DB10a ](https://basescan.org/address/0x5C2531Cbd2cf112Cf687da3Cd536708aDd7DB10a#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xAED282B8aD9257BB1272e93aE63A32A53621e412 ](https://basescan.org/address/0xAED282B8aD9257BB1272e93aE63A32A53621e412#code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x20d7eAd4830b53fB29bb4C4e8a80FD5F1f7d7F2c ](https://www.btrscan.com/address/0x20d7eAd4830b53fB29bb4C4e8a80FD5F1f7d7F2c?tab=Contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x626f8ea9b3B70C03F8cf9a29eFBb9F3b093d1599 ](https://www.btrscan.com/address/0x626f8ea9b3B70C03F8cf9a29eFBb9F3b093d1599?tab=Contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x9d3ce545ffC4d00e372B9733343f001085b045D2 ](https://www.btrscan.com/address/0x9d3ce545ffC4d00e372B9733343f001085b045D2?tab=Contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x29955201601630f686beAF47b0B03be7b86d160F ](https://bscscan.com/address/0x29955201601630f686beAF47b0B03be7b86d160F#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x18c1b03ac8007224FE86442a91fEE3135ba767CD ](https://bscscan.com/address/0x18c1b03ac8007224FE86442a91fEE3135ba767CD#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x705A9Df14b294E6d4E673520369f289bd48C4cCB ](https://bscscan.com/address/0x705A9Df14b294E6d4E673520369f289bd48C4cCB#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xc3D327910415f894CB63f8EDceF886b32bdD5B2F ](https://camp.cloud.blockscout.com/address/0xc3D327910415f894CB63f8EDceF886b32bdD5B2F?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x2cA644C6aB31BA24Ded7DdB846cC246972157Ddd ](https://camp.cloud.blockscout.com/address/0x2cA644C6aB31BA24Ded7DdB846cC246972157Ddd?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x830B51Bde9d7df68cd8Fe790C352FAAcCBa392C3 ](https://camp.cloud.blockscout.com/address/0x830B51Bde9d7df68cd8Fe790C352FAAcCBa392C3?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xB237fdB403992f4AAe0963F5304799242035E22d ](https://celoscan.io/address/0xB237fdB403992f4AAe0963F5304799242035E22d#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x8F5C08671A3986b2B0261FF78f5c2F291208BceC ](https://celoscan.io/address/0x8F5C08671A3986b2B0261FF78f5c2F291208BceC#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x448Babad091267362fe83588838Ed7b192C1dc5A ](https://celoscan.io/address/0x448Babad091267362fe83588838Ed7b192C1dc5A#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x3137e22F379A1Df004DDb10EBbecF1CfD8CbC0e2 ](https://explorer.mainnet.citrea.xyz/address/0x3137e22F379A1Df004DDb10EBbecF1CfD8CbC0e2?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x01Ff31888b1FB51f951273C409f653Dba5Ce8200 ](https://explorer.mainnet.citrea.xyz/address/0x01Ff31888b1FB51f951273C409f653Dba5Ce8200?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x83dd673Fa2C4DE16cAAF0e6109845EeEbb6d1E00 ](https://explorer.mainnet.citrea.xyz/address/0x83dd673Fa2C4DE16cAAF0e6109845EeEbb6d1E00?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x05519a0835a1bFD90f110aA7ca46e9A5F81Ed3b4 ](https://explorer.cronos.org/address/0x05519a0835a1bFD90f110aA7ca46e9A5F81Ed3b4#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x8840F99Bb2D4f69fb02b2d019384C5c98a11746c ](https://explorer.cronos.org/address/0x8840F99Bb2D4f69fb02b2d019384C5c98a11746c#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xE39f6B5Dd03F4ce8C201b946E662E653d94fA121 ](https://explorer.cronos.org/address/0xE39f6B5Dd03F4ce8C201b946E662E653d94fA121#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x295e0aB80f8234398cd0E51C85D873ee69E5F0DD ](https://explorer.cronos.org/address/0x295e0aB80f8234398cd0E51C85D873ee69E5F0DD#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x9aaCAA01F5e6BC876D07f023744E3E0A456a64cf ](https://eden.blockscout.com/address/0x9aaCAA01F5e6BC876D07f023744E3E0A456a64cf?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x59e8C53D383F22b6371b5833504dfAa4136aE6f7 ](https://eden.blockscout.com/address/0x59e8C53D383F22b6371b5833504dfAa4136aE6f7?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xB78BA19a8Bf3202DA7036ec1830222FDC5e0297e ](https://eden.blockscout.com/address/0xB78BA19a8Bf3202DA7036ec1830222FDC5e0297e?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xDa4C5e0f8830002750f788eA729891B4B38EC1c2 ](https://explorer.etherlink.com/address/0xDa4C5e0f8830002750f788eA729891B4B38EC1c2?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x588737013F0302a2fa82Dc03DA06126a81B8be45 ](https://explorer.etherlink.com/address/0x588737013F0302a2fa82Dc03DA06126a81B8be45?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xEe583Ac409a12cc6BD97DD5ca6d2c0ecC8fA86FF ](https://explorer.etherlink.com/address/0xEe583Ac409a12cc6BD97DD5ca6d2c0ecC8fA86FF?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x6FC83ECc0e8142635D77200e5052be8A0a9D2f42 ](https://flare-explorer.flare.network/address/0x6FC83ECc0e8142635D77200e5052be8A0a9D2f42?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xd8237ea1b5974c83C6b0c8942dc2a16F42f789dd ](https://flare-explorer.flare.network/address/0xd8237ea1b5974c83C6b0c8942dc2a16F42f789dd?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x9730d0B30d9145B66a8e09D26295e36cb84F64a9 ](https://flare-explorer.flare.network/address/0x9730d0B30d9145B66a8e09D26295e36cb84F64a9?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x711bCE12269a3a496eFaABB8B9AD5A4485E08A24 ](https://fraxscan.com/address/0x711bCE12269a3a496eFaABB8B9AD5A4485E08A24) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xa036C78AE8e162feD4db4abbD41f79995F28bC4b ](https://fraxscan.com/address/0xa036C78AE8e162feD4db4abbD41f79995F28bC4b) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x50d4e8af118db0D5b301B18Ef37435F987Fe2D2B ](https://fraxscan.com/address/0x50d4e8af118db0D5b301B18Ef37435F987Fe2D2B) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xe2558155AEcEF57cAADB98e39b0538ab0ae95693 ](https://gensyn-mainnet.explorer.alchemy.com/address/0xe2558155AEcEF57cAADB98e39b0538ab0ae95693?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x155134544AE2Ec3AB23034BF620538482C5E3c40 ](https://gensyn-mainnet.explorer.alchemy.com/address/0x155134544AE2Ec3AB23034BF620538482C5E3c40?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xdaE77f687883D656Aa4dc7fF89c0c891510C61A5 ](https://gensyn-mainnet.explorer.alchemy.com/address/0xdaE77f687883D656Aa4dc7fF89c0c891510C61A5?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x778aCb5109ba6f36C7FCfD0997E2d8501A555C9e ](https://gnosis.blockscout.com/address/0x778aCb5109ba6f36C7FCfD0997E2d8501A555C9e?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xfeC864c0aE0556a36699A0db276DF5a73D095dA1 ](https://gnosis.blockscout.com/address/0xfeC864c0aE0556a36699A0db276DF5a73D095dA1?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x721705802bE850ccBEd14E869E83c962910aF842 ](https://gnosis.blockscout.com/address/0x721705802bE850ccBEd14E869E83c962910aF842?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x3c75C433e7902193497617EaFCc8385A3D031836 ](https://explorer.hemi.xyz/address/0x3c75C433e7902193497617EaFCc8385A3D031836) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x0d9E428075b8A691e12237984b8284E40ab9363A ](https://explorer.hemi.xyz/address/0x0d9E428075b8A691e12237984b8284E40ab9363A) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xbd30B731C881149e2BA23C7fd375D5608208Ecb3 ](https://explorer.hemi.xyz/address/0xbd30B731C881149e2BA23C7fd375D5608208Ecb3) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xD7217E5687FF1071356C780b5fe4803D9D967da7 ](https://hyperevmscan.io/address/0xD7217E5687FF1071356C780b5fe4803D9D967da7#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xdf5202e29654e02011611A086f15477880580CAc ](https://hyperevmscan.io/address/0xdf5202e29654e02011611A086f15477880580CAc#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xaEff6Ef4B7bbfbAadB18b634A8F11392CBeB72Be ](https://hyperevmscan.io/address/0xaEff6Ef4B7bbfbAadB18b634A8F11392CBeB72Be#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x857B55cEb57dA0C2A83EE08a8dB529B931089aee ](https://hyperevmscan.io/address/0x857B55cEb57dA0C2A83EE08a8dB529B931089aee#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x056dd7D4B373ED26c788190085CC6C52B8e7479d ](https://hyperevmscan.io/address/0x056dd7D4B373ED26c788190085CC6C52B8e7479d#code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x35587F8d98eA305FB762934a63F3c1564037F9C7 ](https://explorer.inkonchain.com/address/0x35587F8d98eA305FB762934a63F3c1564037F9C7) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x92A070b2b4Af436ba4a168451fb360e45b849355 ](https://explorer.inkonchain.com/address/0x92A070b2b4Af436ba4a168451fb360e45b849355) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xe7D687a017B549fe723E78a6Bc1206216C701821 ](https://explorer.inkonchain.com/address/0xe7D687a017B549fe723E78a6Bc1206216C701821) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xf2Aecd4a4d4C21d08770e34F392C4C271aBD9144 ](https://kaiascan.io/address/0xf2Aecd4a4d4C21d08770e34F392C4C271aBD9144?tabId=contract\&page=1) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x4d04C39ca604b560c50F4045c558378FD9AEBCF4 ](https://kaiascan.io/address/0x4d04C39ca604b560c50F4045c558378FD9AEBCF4?tabId=contract\&page=1) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xfCA12228DA5fba6E9c0B57a8e8322d0eBaCa03Bc ](https://kaiascan.io/address/0xfCA12228DA5fba6E9c0B57a8e8322d0eBaCa03Bc?tabId=contract\&page=1) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x3b369B37eba1655e8c44bC08E3A604D592c4a14F ](https://kaiascan.io/address/0x3b369B37eba1655e8c44bC08E3A604D592c4a14F?tabId=contract\&page=1) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xFcb8b57E56787bB29e130Fca67f3c5a1232975D1 ](https://katanascan.com/address/0xFcb8b57E56787bB29e130Fca67f3c5a1232975D1#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xc8D22B1adD3D176600E9952e7876e9249254cAAF ](https://katanascan.com/address/0xc8D22B1adD3D176600E9952e7876e9249254cAAF#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x6d6A3ba62836d6B40277767dCAc8fd390d4BcedC ](https://katanascan.com/address/0x6d6A3ba62836d6B40277767dCAc8fd390d4BcedC#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xA9132a09838fD20304dF2B2892679d06A4cc6371 ](https://katanascan.com/address/0xA9132a09838fD20304dF2B2892679d06A4cc6371#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xd952175e940D97775cBC5a523977a6f091D0d702 ](https://katanascan.com/address/0xd952175e940D97775cBC5a523977a6f091D0d702#code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x5DC11CF8BA4C39d1194F91218D35008d9F52A5d0 ](https://lineascan.build/address/0x5DC11CF8BA4C39d1194F91218D35008d9F52A5d0#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x6FaF26DD640e22457cA4fd5DA702BA3E169eEd87 ](https://lineascan.build/address/0x6FaF26DD640e22457cA4fd5DA702BA3E169eEd87#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xcAB7C66F7191Ad3Ef1e7fEeb67F3137BC975F8cE ](https://lineascan.build/address/0xcAB7C66F7191Ad3Ef1e7fEeb67F3137BC975F8cE#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x122Ea8ff8888C29F8736665d576e3fAEF15D27D5 ](https://lineascan.build/address/0x122Ea8ff8888C29F8736665d576e3fAEF15D27D5#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x8DB1483C64384FA8581D6e6e82C6F44812090c2d ](https://blockscout.lisk.com/address/0x8DB1483C64384FA8581D6e6e82C6F44812090c2d?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x382d00918B744Bd91B906f38CAe54e67649E770C ](https://blockscout.lisk.com/address/0x382d00918B744Bd91B906f38CAe54e67649E770C?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x3f4A754Af683a1b9AD7E20608630bED3B459d230 ](https://blockscout.lisk.com/address/0x3f4A754Af683a1b9AD7E20608630bED3B459d230?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xf133FA5A78C398B31Cc4a180E6Ae84111D6DCF5B ](https://megaeth.blockscout.com/address/0xf133FA5A78C398B31Cc4a180E6Ae84111D6DCF5B?tab=contract_code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x00a58b7a9B3E86CB21f5F11f29F4A12346457012 ](https://megaeth.blockscout.com/address/0x00a58b7a9B3E86CB21f5F11f29F4A12346457012?tab=contract_code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x3aE18af9717C734820137726967bef4fBc5Ec95c ](https://megaeth.blockscout.com/address/0x3aE18af9717C734820137726967bef4fBc5Ec95c?tab=contract_code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xB4A1B0EF18d169c19fC7617aCE898A06Dc495a7C ](https://megaeth.blockscout.com/address/0xB4A1B0EF18d169c19fC7617aCE898A06Dc495a7C?tab=contract_code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x68DCEA6df0f07385946AA0cDA2648c27a050e26e ](https://explorer.mode.network/address/0x68DCEA6df0f07385946AA0cDA2648c27a050e26e) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x97CF0f15bf580838900161F2a4D7CE9BC88E8d5D ](https://explorer.mode.network/address/0x97CF0f15bf580838900161F2a4D7CE9BC88E8d5D) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x8dBDae88260aAE80f195c0CBFBa5b0917E8B3296 ](https://explorer.mode.network/address/0x8dBDae88260aAE80f195c0CBFBa5b0917E8B3296) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [0x8B2F922162FBb60A6a072cC784A2E4168fB0bb0c](https://monadscan.com/address/0x8B2F922162FBb60A6a072cC784A2E4168fB0bb0c#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [0x9f3c0999425656fD189C69a8aD68cB64986D644A](https://monadscan.com/address/0x9f3c0999425656fD189C69a8aD68cB64986D644A#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [0xa00666E86C7e2FA8d2c78d9481E687e098340180](https://monadscan.com/address/0xa00666E86C7e2FA8d2c78d9481E687e098340180#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [0x6a42f8b46224baA4DbBBc2F860F4675eeA7bd52B](https://monadscan.com/address/0x6a42f8b46224baA4DbBBc2F860F4675eeA7bd52B#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x0A503aB026EFACBC0F7feE7795F34B80b5B9a662 ](https://monadscan.com/address/0x0A503aB026EFACBC0F7feE7795F34B80b5B9a662#code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x7D8BF8B276f967F7539c9e91E1a85a33fefE612B ](https://explorer.morph.network/address/0x7D8BF8B276f967F7539c9e91E1a85a33fefE612B?tab=contract_code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xa01D7c41cf419405d4DF2e5750d26438DCAC28a6 ](https://explorer.morph.network/address/0xa01D7c41cf419405d4DF2e5750d26438DCAC28a6?tab=contract_code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x553c013d1978CF82EF6b316f5c247B73718ba481 ](https://explorer.morph.network/address/0x553c013d1978CF82EF6b316f5c247B73718ba481?tab=contract_code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x20d990D9eBf8003Df8cAD3Aa36aeF4404e3Ccb86 ](https://explorer.morph.network/address/0x20d990D9eBf8003Df8cAD3Aa36aeF4404e3Ccb86?tab=contract_code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x6128b680b277Bf4Df80DFE9D8c55A498660870ef ](https://optimistic.etherscan.io/address/0x6128b680b277Bf4Df80DFE9D8c55A498660870ef#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xEe9F7C64dD827ED7b5CAA2272936366FAca00CF3 ](https://optimistic.etherscan.io/address/0xEe9F7C64dD827ED7b5CAA2272936366FAca00CF3#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x71B299bDb52b6396429cd1E11c418324502CB434 ](https://optimistic.etherscan.io/address/0x71B299bDb52b6396429cd1E11c418324502CB434#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xD1346be260cd22Eab9E6163010b0D5CbfAAAD32b ](https://optimistic.etherscan.io/address/0xD1346be260cd22Eab9E6163010b0D5CbfAAAD32b#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xc6945A915Bb7e2A365469f120A33D2FA42951cF3 ](https://optimistic.etherscan.io/address/0xc6945A915Bb7e2A365469f120A33D2FA42951cF3) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | 0x8E01ed1E1A41029b3137FcE9Aa880c0A54827498 | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | 0xe510e1fcC429943cA3455A7bfBD79f0307Cd8403 | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | 0xbe858d729548eB49BbFA05Acd3674ca8cdaAdD4b | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xD7373D3597C26e7340B0612C938EEFE6DE02Ab30 ](https://plasmascan.to/address/0xD7373D3597C26e7340B0612C938EEFE6DE02Ab30/contract/9745/code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x42432F31D085ECb8D9C128092c84b084123C624e ](https://plasmascan.to/address/0x42432F31D085ECb8D9C128092c84b084123C624e/contract/9745/code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xB241753668F929EB78A0069330c31305C0C83255 ](https://plasmascan.to/address/0xB241753668F929EB78A0069330c31305C0C83255/contract/9745/code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x171b2807E8ce8fb853C37bc3C3b4805aBf6f9896 ](https://plasmascan.to/address/0x171b2807E8ce8fb853C37bc3C3b4805aBf6f9896/contract/9745/code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x4f0a370bb367843CFd914c4d9972523aD2f8FCc9 ](https://explorer.plume.org/address/0x4f0a370bb367843CFd914c4d9972523aD2f8FCc9?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x5935fFcD1C5D269840ae7c685bC957A73E04AEDB ](https://explorer.plume.org/address/0x5935fFcD1C5D269840ae7c685bC957A73E04AEDB?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xB7c243AfACb25870775ADFdAe9D0EAc2324dD152 ](https://explorer.plume.org/address/0xB7c243AfACb25870775ADFdAe9D0EAc2324dD152?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x60d3184BDD31BAE7De973894B3bA0b3B6900B79a ](https://explorer.plume.org/address/0x60d3184BDD31BAE7De973894B3bA0b3B6900B79a?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xC11a53eE9B1eCc7a068D8e40F8F17926584F97Cf ](https://polygonscan.com/address/0xC11a53eE9B1eCc7a068D8e40F8F17926584F97Cf#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xEb174FEA51Da241eB3B516959B216e013de2888a ](https://polygonscan.com/address/0xEb174FEA51Da241eB3B516959B216e013de2888a#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xc0006f52B38625C283dd2f972dD9B779A5851Dd0 ](https://polygonscan.com/address/0xc0006f52B38625C283dd2f972dD9B779A5851Dd0#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xb70a43821d2707fA9d0EDd9511CC499F468Ba564 ](https://polygonscan.com/address/0xb70a43821d2707fA9d0EDd9511CC499F468Ba564#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xAb06a92cd253Bc12Dec8f719a693a6b472CCDfF4 ](https://polygonscan.com/address/0xAb06a92cd253Bc12Dec8f719a693a6b472CCDfF4) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x0B84EdB54100687Cca40041bE88b17c4DD8FCEb0 ](https://explorer.risechain.com/address/0x0B84EdB54100687Cca40041bE88b17c4DD8FCEb0?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x834FA78df6de3f391fBAb743D194AC7A7ac8E8A5 ](https://explorer.risechain.com/address/0x834FA78df6de3f391fBAb743D194AC7A7ac8E8A5?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x189110d96Eeec6682B4E78d23495c4B8edEeE125 ](https://explorer.risechain.com/address/0x189110d96Eeec6682B4E78d23495c4B8edEeE125?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x0FBad98595b0186dA120E41f77C102beb49f803c ](https://robinhoodchain.blockscout.com/address/0x0FBad98595b0186dA120E41f77C102beb49f803c?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x7a91222F3f7B927bB8fb624593Ca86e111C2F85e ](https://robinhoodchain.blockscout.com/address/0x7a91222F3f7B927bB8fb624593Ca86e111C2F85e?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x79370Ed003CE325C088E530d5e8655c99c2993e1 ](https://robinhoodchain.blockscout.com/address/0x79370Ed003CE325C088E530d5e8655c99c2993e1?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xe785a2eFD384BA7B95BaEd3851BC76aeD67C676f ](https://robinhoodchain.blockscout.com/address/0xe785a2eFD384BA7B95BaEd3851BC76aeD67C676f?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xCe5c1aFa115fF8b1D6913509bfc79D9AE08CC857 ](https://robinhoodchain.blockscout.com/address/0xCe5c1aFa115fF8b1D6913509bfc79D9AE08CC857?tab=contract) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x474cdCF6B3be2eb770065b88d2F7c57A9BC609E0 ](https://scrollscan.com/address/0x474cdCF6B3be2eb770065b88d2F7c57A9BC609E0) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x3199Ddb2aA394B175a814EB79BB654822Ee1100F ](https://scrollscan.com/address/0x3199Ddb2aA394B175a814EB79BB654822Ee1100F) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x0ED73cc76a0ebd7C5a6a95397718D8F1dCC219b1 ](https://scrollscan.com/address/0x0ED73cc76a0ebd7C5a6a95397718D8F1dCC219b1) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x30f5b078C80bD06fEdc3B40b4a4441a96Dd9cf22 ](https://seiscan.io/address/0x30f5b078C80bD06fEdc3B40b4a4441a96Dd9cf22#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xbADd49F7db90f65fF5822681AA6B8548E8356a1D ](https://seiscan.io/address/0xbADd49F7db90f65fF5822681AA6B8548E8356a1D#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x26abEaee65A878E9Fe8F99fEb31aec62fbA2624E ](https://seiscan.io/address/0x26abEaee65A878E9Fe8F99fEb31aec62fbA2624E#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x783b4853Da42DBA4A86eFa4b94ABd48100c6D982 ](https://soneium.blockscout.com/address/0x783b4853Da42DBA4A86eFa4b94ABd48100c6D982?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xd25Ae31a2480DF33b7E5F8CfEE4229248309d519 ](https://soneium.blockscout.com/address/0xd25Ae31a2480DF33b7E5F8CfEE4229248309d519?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x01eD6405cDf9784022c5466eA1091c78f46B829f ](https://soneium.blockscout.com/address/0x01eD6405cDf9784022c5466eA1091c78f46B829f?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xc8BE2FD6f65FB3ce25Dd6a50F21A9245B9E399d7 ](https://sonicscan.org/address/0xc8BE2FD6f65FB3ce25Dd6a50F21A9245B9E399d7) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xc49224e28992E693aFaa778a6F54c329E5Ac9704 ](https://sonicscan.org/address/0xc49224e28992E693aFaa778a6F54c329E5Ac9704) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x1fbF65D5C905ac9144afbB2f410F4e12F69edF5D ](https://sonicscan.org/address/0x1fbF65D5C905ac9144afbB2f410F4e12F69edF5D) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x7fc35488803D49D00a94b206A223f7661898BE3a ](https://stablescan.xyz/address/0x7fc35488803D49D00a94b206A223f7661898BE3a#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x4EF83ACD552598a1196c1aBDD0bA2EdE6f2237B4 ](https://stablescan.xyz/address/0x4EF83ACD552598a1196c1aBDD0bA2EdE6f2237B4#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x9282DBa3d1788f4f02B5DdFc4fc5985e70197620 ](https://stablescan.xyz/address/0x9282DBa3d1788f4f02B5DdFc4fc5985e70197620#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xCe93fcB2849EB886F1e81d45D2747dF803f843C3 ](https://stablescan.xyz/address/0xCe93fcB2849EB886F1e81d45D2747dF803f843C3#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x5C884d4B1510EAd302EC50A2AB4DE9c0b9E407ce ](https://stablescan.xyz/address/0x5C884d4B1510EAd302EC50A2AB4DE9c0b9E407ce#code) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x0437C5B0CF1edFb8309613E4fEBE2a512D9a735d ](https://explorer.tac.build/address/0x0437C5B0CF1edFb8309613E4fEBE2a512D9a735d?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xabA00365C6284548F90480993fc46dbB7775FB96 ](https://explorer.tac.build/address/0xabA00365C6284548F90480993fc46dbB7775FB96?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x784125737238e058B646FDB502F5B6d940713B95 ](https://explorer.tac.build/address/0x784125737238e058B646FDB502F5B6d940713B95?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x3DE400E3F79113194fa5AF6Ae5C474947E0C82Db ](https://explore.tempo.xyz/address/0x3DE400E3F79113194fa5AF6Ae5C474947E0C82Db?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0x669771F03ab55CebF753E90C3c9D80ad9391cf25 ](https://explore.tempo.xyz/address/0x669771F03ab55CebF753E90C3c9D80ad9391cf25?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xF85aD5f14cC903533FC409B8098B58b4C2f36697 ](https://explore.tempo.xyz/address/0xF85aD5f14cC903533FC409B8098B58b4C2f36697?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xB118227C728b5ce52445a5aAee48C8a281C429E0 ](https://explore.tempo.xyz/address/0xB118227C728b5ce52445a5aAee48C8a281C429E0?tab=contract) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0xDC9693CE6488640faEf173Ec2635ff99fdC25a07 ](https://explore.tempo.xyz/address/0xDC9693CE6488640faEf173Ec2635ff99fdC25a07?tab=contract) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xC9b34c108014B44e5a189A830e7e04c56704a0c9 ](https://uniscan.xyz/address/0xC9b34c108014B44e5a189A830e7e04c56704a0c9#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xf1Ab9e885C0faa0cbCEd407498BBA895537aD754 ](https://uniscan.xyz/address/0xf1Ab9e885C0faa0cbCEd407498BBA895537aD754#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x9a13bdA35F98811fbAcf097966b2C838f3F9c58C ](https://uniscan.xyz/address/0x9a13bdA35F98811fbAcf097966b2C838f3F9c58C#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xB9130D2A87d7c60ED7E7e4b25bdA6e3E6841becB ](https://uniscan.xyz/address/0xB9130D2A87d7c60ED7E7e4b25bdA6e3E6841becB#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x2b7Bf2f2027bcfE3A1F6Bc93EA80220a883a6851 ](https://uniscan.xyz/address/0x2b7Bf2f2027bcfE3A1F6Bc93EA80220a883a6851) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x6846EA318B6B987Ee6b28eBFd87c3409F1d13108 ](https://worldscan.org/address/0x6846EA318B6B987Ee6b28eBFd87c3409F1d13108#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoVaultV1 AdapterFactory | [ 0xbF7DEa3756668C7E396C655D646C039826ba8416 ](https://worldscan.org/address/0xbF7DEa3756668C7E396C655D646C039826ba8416#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xEd0b06fcdDB6dD0985e2de9D22ad034d313b7dBd ](https://worldscan.org/address/0xEd0b06fcdDB6dD0985e2de9D22ad034d313b7dBd#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x06A47994B4890dcA28C076969cedE1151d86EFCF ](https://worldscan.org/address/0x06A47994B4890dcA28C076969cedE1151d86EFCF#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Blue Public Allocator | [ 0x5Fe47f63ACd84f8A69b97E0a5122fCBff08Df48F ](https://worldscan.org/address/0x5Fe47f63ACd84f8A69b97E0a5122fCBff08Df48F) | [blue-public-allocator](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator) | | Contract | Address | Source Code | | :------------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x227544d6989cD15c05AAB6dde4F29523dcfdbe2B ](https://xdcscan.com/address/0x227544d6989cD15c05AAB6dde4F29523dcfdbe2B#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x5C00c99F2235439725417E9f037B7D38FfF35d31 ](https://xdcscan.com/address/0x5C00c99F2235439725417E9f037B7D38FfF35d31#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x79A8C4e9E502C1867cAf2E7202f0C6b89aaCd5c1 ](https://xdcscan.com/address/0x79A8C4e9E502C1867cAf2E7202f0C6b89aaCd5c1#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0x49bbF0Da22c66083ED7F2c0CaA5916C8b94eb5F1 ](https://explorer.zircuit.com/address/0x49bbF0Da22c66083ED7F2c0CaA5916C8b94eb5F1?activeTab=3) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x8a3eb8B2fb498c40D15CDf7F77dAb8C1891E559E ](https://explorer.zircuit.com/address/0x8a3eb8B2fb498c40D15CDf7F77dAb8C1891E559E?activeTab=3) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0x9d34f6682F45361688472E06E49B8795Ac46796F ](https://explorer.zircuit.com/address/0x9d34f6682F45361688472E06E49B8795Ac46796F?activeTab=3) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xE3a2CEbca662d99D0F279aF13a6bb8c9825D2ea0 ](https://sepolia.basescan.org/address/0xE3a2CEbca662d99D0F279aF13a6bb8c9825D2ea0#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0xa24674Cc2c603FBA8AE6b442E3dB05f3dDaAa3D5 ](https://sepolia.basescan.org/address/0xa24674Cc2c603FBA8AE6b442E3dB05f3dDaAa3D5#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xCED92dd29B6E99120DD41CB79321126Abd5852b2 ](https://sepolia.basescan.org/address/0xCED92dd29B6E99120DD41CB79321126Abd5852b2#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | | Contract | Address | Source Code | | :------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | | VaultV2Factory | [ 0xb3fE2D5f8Af90f194B01db546397058Fcebb85D1 ](https://sepolia.etherscan.io/address/0xb3fE2D5f8Af90f194B01db546397058Fcebb85D1#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoMarketV1 AdapterV2Factory | [ 0x5481f54CACdf0aECE9f71F2Bb2c0A8e44b2d5D81 ](https://sepolia.etherscan.io/address/0x5481f54CACdf0aECE9f71F2Bb2c0A8e44b2d5D81#code) | [vault-v2](https://github.com/morpho-org/vault-v2) | | MorphoRegistry | [ 0xE8eE811C7c9748De2db8c878dc5e32c9DD12dAB3 ](https://sepolia.etherscan.io/address/0xE8eE811C7c9748De2db8c878dc5e32c9DD12dAB3#code) | [vault-v2](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/registries) | ## Morpho Vault V1 [#morpho-vault-v1] Retired allocator addresses for Vault V1 are intentionally omitted. Public Allocator integrators should use the [Vault V2 guide](/developers/borrow/tutorials/public-allocator/) and the [Morpho Vault V2 address section](#morpho-vault-v2). | Contract | Address | Source Code | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x1897A8997241C1cD4bD0698647e4EB7213535c24 ](https://etherscan.io/address/0x1897A8997241C1cD4bD0698647e4EB7213535c24#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | MetaMorpho Factory \[OLD] | [ 0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101 ](https://etherscan.io/address/0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101#code) | [metamorpho](https://github.com/morpho-org/metamorpho/tree/c5e758da97d210ede921bf228d37d31e744bd5bf) | | Contract | Address | Source Code | | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x83A7f60c9fc57cEf1e8001bda98783AA1A53E4b1 ](https://abscan.org/address/0x83A7f60c9fc57cEf1e8001bda98783AA1A53E4b1#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x878988f5f561081deEa117717052164ea1Ef0c82 ](https://arbiscan.io/address/0x878988f5f561081deEa117717052164ea1Ef0c82) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xFf62A7c278C62eD665133147129245053Bbf5918 ](https://basescan.org/address/0xFf62A7c278C62eD665133147129245053Bbf5918#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | MetaMorpho Factory \[OLD] | [ 0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101 ](https://basescan.org/address/0xA9c3D3a366466Fa809d1Ae982Fb2c46E5fC41101#code) | [metamorpho](https://github.com/morpho-org/metamorpho/tree/c5e758da97d210ede921bf228d37d31e744bd5bf) | | Contract | Address | Source Code | | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xb95De4a9C81Ba6240378F383f88592d30937d048 ](https://www.btrscan.com/address/0xb95De4a9C81Ba6240378F383f88592d30937d048?tab=Contract) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x92983687e672cA6d96530f9Dbe11a196cE905d72 ](https://bscscan.com/address/0x92983687e672cA6d96530f9Dbe11a196cE905d72#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x3F4b9246b7Cd3F7671c70BeBd5AAFC08e5bb5f16 ](https://camp.cloud.blockscout.com/address/0x3F4b9246b7Cd3F7671c70BeBd5AAFC08e5bb5f16?tab=contract) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x6870aA9f66C1e5Efe8Dbe8730e86E9e91f688275 ](https://celoscan.io/address/0x6870aA9f66C1e5Efe8Dbe8730e86E9e91f688275#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xEA67e5566Ca2c0176d9db172A7f9A1e1F22E9D3A ](https://explorer.cronos.org/address/0xEA67e5566Ca2c0176d9db172A7f9A1e1F22E9D3A#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x997a79c3C04c5B9eb27d343ae126bcCFb5D74781 ](https://explorer.etherlink.com/address/0x997a79c3C04c5B9eb27d343ae126bcCFb5D74781?tab=contract) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x27D4Af0AC9E7FDfA6D0853236f249CC27AE79488 ](https://fraxscan.com/address/0x27D4Af0AC9E7FDfA6D0853236f249CC27AE79488) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xFf3623eAdB1DD8590b902fA23baCfaB3c361Bf68 ](https://gnosis.blockscout.com/address/0xFf3623eAdB1DD8590b902fA23baCfaB3c361Bf68?tab=contract) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x8e52179BeB18E882040b01632440d8Ca0f01da82 ](https://explorer.hemi.xyz/address/0x8e52179BeB18E882040b01632440d8Ca0f01da82) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xec051b19d654C48c357dC974376DeB6272f24e53 ](https://hyperevmscan.io/address/0xec051b19d654C48c357dC974376DeB6272f24e53#code) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xd3f39505d0c48AFED3549D625982FdC38Ea9904b ](https://explorer.inkonchain.com/address/0xd3f39505d0c48AFED3549D625982FdC38Ea9904b) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x1c8De6889acee12257899BFeAa2b7e534de32E16 ](https://katanascan.com/address/0x1c8De6889acee12257899BFeAa2b7e534de32E16#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xA148a8223B622A72dC36472DE1492aBb5c089BA7 ](https://lineascan.build/address/0xA148a8223B622A72dC36472DE1492aBb5c089BA7#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x01dD876130690469F685a65C2B295A90a81BaD91 ](https://blockscout.lisk.com/address/0x01dD876130690469F685a65C2B295A90a81BaD91?tab=contract) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xae5b0884bfff430493D6C844B9fd052Af7d79278 ](https://explorer.mode.network/address/0xae5b0884bfff430493D6C844B9fd052Af7d79278) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [0x33f20973275B2F574488b18929cd7DCBf1AbF275](https://monadscan.com/address/0x33f20973275B2F574488b18929cd7DCBf1AbF275#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x3Bb6A6A0Bc85b367EFE0A5bAc81c5E52C892839a ](https://optimistic.etherscan.io/address/0x3Bb6A6A0Bc85b367EFE0A5bAc81c5E52C892839a) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x69410429099018fa1586aAB0aFADC525314f5830 ](https://plasmascan.to/address/0x69410429099018fa1586aAB0aFADC525314f5830/contract/9745/code) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x2525D453D9BA13921D5aB5D8c12F9202b0e19456 ](https://explorer.plume.org/address/0x2525D453D9BA13921D5aB5D8c12F9202b0e19456?tab=contract) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xa9c87daB340631C34BB738625C70499e29ddDC98 ](https://polygonscan.com/address/0xa9c87daB340631C34BB738625C70499e29ddDC98) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x56b65742ade55015e6480959808229Ad6dbc9295 ](https://scrollscan.com/address/0x56b65742ade55015e6480959808229Ad6dbc9295) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x8Dea49ec5bd5AeAc8bcf96B3E187F59354118291 ](https://seiscan.io/address/0x8Dea49ec5bd5AeAc8bcf96B3E187F59354118291#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x7026b436f294e560b3C26E731f5cac5992cA2B33 ](https://soneium.blockscout.com/address/0x7026b436f294e560b3C26E731f5cac5992cA2B33?tab=contract) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x0cE9e3512CB4df8ae7e265e62Fb9258dc14f12e8 ](https://sonicscan.org/address/0x0cE9e3512CB4df8ae7e265e62Fb9258dc14f12e8) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xb4ae5673c48621189E2bEfBA96F31912032DD1AE ](https://stablescan.xyz/address/0xb4ae5673c48621189E2bEfBA96F31912032DD1AE#code) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xcDA78f4979d17Ec93052A84A12001fe0088AD734 ](https://explorer.tac.build/address/0xcDA78f4979d17Ec93052A84A12001fe0088AD734?tab=contract) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xe9EdE3929F43a7062a007C3e8652e4ACa610Bdc0 ](https://uniscan.xyz/address/0xe9EdE3929F43a7062a007C3e8652e4ACa610Bdc0) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x4DBB3a642a2146d5413750Cca3647086D9ba5F12 ](https://worldscan.org/address/0x4DBB3a642a2146d5413750Cca3647086D9ba5F12) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0xd2c9068aD68c4c9F1A4fE1Ea650BdFE13DC5EaF1 ](https://explorer.zircuit.com/address/0xd2c9068aD68c4c9F1A4fE1Ea650BdFE13DC5EaF1?activeTab=3) | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x2c3FE6D71F8d54B063411Abb446B49f13725F784 ](https://sepolia.basescan.org/address/0x2c3FE6D71F8d54B063411Abb446B49f13725F784) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | | Contract | Address | Source Code | | :---------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | MetaMorpho Factory V1.1 | [ 0x98CbFE4053ad6778E0E3435943aC821f565D0b03 ](https://sepolia.etherscan.io/address/0x98CbFE4053ad6778E0E3435943aC821f565D0b03) | [morpho-org/metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1/tree/5a0717b2c863060ff452ca324716083fe6d09374/src) | ## Bundles [#bundles] | Contract | Address | Source Code | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x02912516d49dE997db75B9D7858faAE59209650B ](https://etherscan.io/address/0x02912516d49dE997db75B9D7858faAE59209650B#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xaf85aF286637A033BE7d59ED8cC566afa3309B02 ](https://etherscan.io/address/0xaf85aF286637A033BE7d59ED8cC566afa3309B02#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x38B0C12AB81976e9417D4ebfe2A34DB6DF22e6AD ](https://etherscan.io/address/0x38B0C12AB81976e9417D4ebfe2A34DB6DF22e6AD#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | MidnightBundlesV1 | [ 0x7c00dBB2b6b6b9B28745332e550dC8782Fcf77EC ](https://etherscan.io/address/0x7c00dBB2b6b6b9B28745332e550dC8782Fcf77EC#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/midnight/MidnightBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x5afAb0B2414E30c92a708234ea1b383Ce6317ED5 ](https://arbiscan.io/address/0x5afAb0B2414E30c92a708234ea1b383Ce6317ED5) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0x7B885a940164eD51A068725f577a12197b76109b ](https://arbiscan.io/address/0x7B885a940164eD51A068725f577a12197b76109b) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x30a388A64b99192702a83D2CA9B95D79702afbf2 ](https://arbiscan.io/address/0x30a388A64b99192702a83D2CA9B95D79702afbf2#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x2B08A911f48dE25A7e305D910Afb5597aBE8ea7B ](https://basescan.org/address/0x2B08A911f48dE25A7e305D910Afb5597aBE8ea7B#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xE52E169C342C096C4949ABb944DC9f30E3F5Ea84 ](https://basescan.org/address/0xE52E169C342C096C4949ABb944DC9f30E3F5Ea84#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x4D28D900e381eCE4B351302f1Abe588496793A2b ](https://basescan.org/address/0x4D28D900e381eCE4B351302f1Abe588496793A2b#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | MidnightBundlesV1 | [ 0x091183d729BE9f808c212b475E387A12E67850A7 ](https://basescan.org/address/0x091183d729BE9f808c212b475E387A12E67850A7#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/midnight/MidnightBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0xB5173417e28482c61C14A4C2e217b158fF0db666 ](https://hyperevmscan.io/address/0xB5173417e28482c61C14A4C2e217b158fF0db666#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xC1749C8d50bc645D5116ccf4C858Bc45cB981Ac4 ](https://hyperevmscan.io/address/0xC1749C8d50bc645D5116ccf4C858Bc45cB981Ac4#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x84849171E1783630E1C4253b9C9d4b4208b0D86A ](https://hyperevmscan.io/address/0x84849171E1783630E1C4253b9C9d4b4208b0D86A#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x2464F4d0a4481732e7cC90ADD5abF986A48A06Dd ](https://katanascan.com/address/0x2464F4d0a4481732e7cC90ADD5abF986A48A06Dd#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xa434ABcc7e945b804c87B4f3c0a76b20651d4863 ](https://katanascan.com/address/0xa434ABcc7e945b804c87B4f3c0a76b20651d4863#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0xCA52e5B901D9939013Fa8744dCbDeE0B6BdD5B39 ](https://katanascan.com/address/0xCA52e5B901D9939013Fa8744dCbDeE0B6BdD5B39#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0xcDDc5311A7ccDb2A7Bf97299149bE1D687F3C76e ](https://monadscan.com/address/0xcDDc5311A7ccDb2A7Bf97299149bE1D687F3C76e#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xB04b831893A6E2E02Be347cD259690c5Bc7D0675 ](https://monadscan.com/address/0xB04b831893A6E2E02Be347cD259690c5Bc7D0675#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x9C76E91bf08E712a713e3baB2F5AE01f6ec8845A ](https://monadscan.com/address/0x9C76E91bf08E712a713e3baB2F5AE01f6ec8845A#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x112088363eE4CcF3F37EaBD98FDDeEd98F0Be485 ](https://optimistic.etherscan.io/address/0x112088363eE4CcF3F37EaBD98FDDeEd98F0Be485) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0x80De0F063aC662a4ee86c2F4Db0b52746094ad62 ](https://optimistic.etherscan.io/address/0x80De0F063aC662a4ee86c2F4Db0b52746094ad62) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x317ca3534f8Cf92Accb4b4366F48017E596bc34a ](https://optimistic.etherscan.io/address/0x317ca3534f8Cf92Accb4b4366F48017E596bc34a#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x386ABE0188b7FADA897cE4dF9d72E8F57915B103 ](https://polygonscan.com/address/0x386ABE0188b7FADA897cE4dF9d72E8F57915B103) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0x7Ae2B7012c82ea18a6BeE98ad09a684C88d6e36a ](https://polygonscan.com/address/0x7Ae2B7012c82ea18a6BeE98ad09a684C88d6e36a) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x93a1B342f3DF2c2bb5D0d8D5AF819450fFA4bE78 ](https://polygonscan.com/address/0x93a1B342f3DF2c2bb5D0d8D5AF819450fFA4bE78#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0xcC108538f36242D6E0d6B9255f6D9Ccd137D70Fe ](https://robinhoodchain.blockscout.com/address/0xcC108538f36242D6E0d6B9255f6D9Ccd137D70Fe?tab=contract) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xCE29862924756584BBD0D75CA1249d22007E2813 ](https://robinhoodchain.blockscout.com/address/0xCE29862924756584BBD0D75CA1249d22007E2813?tab=contract) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x53A1eB6589861F686af7c531211E35Aefe30210f ](https://robinhoodchain.blockscout.com/address/0x53A1eB6589861F686af7c531211E35Aefe30210f?tab=contract) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x2b910f5368e4939A2906ADa85c21fc0e51C4A861 ](https://stablescan.xyz/address/0x2b910f5368e4939A2906ADa85c21fc0e51C4A861#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0x258d5c815CCE7017E24c63a7669F51ABcD0Dd4e5 ](https://stablescan.xyz/address/0x258d5c815CCE7017E24c63a7669F51ABcD0Dd4e5#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0xFB606389166c04828D6Dba36F77871489673CeA0 ](https://stablescan.xyz/address/0xFB606389166c04828D6Dba36F77871489673CeA0#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0xe8aA1d8f1Cb111B7f52957D662Ee310D6d2Ee9B9 ](https://explore.tempo.xyz/address/0xe8aA1d8f1Cb111B7f52957D662Ee310D6d2Ee9B9?tab=contract) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0x8225192b8638bDe9D41a6d96aBb824F660Ef57E1 ](https://explore.tempo.xyz/address/0x8225192b8638bDe9D41a6d96aBb824F660Ef57E1?tab=contract) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0xAE863452f44ADD237739A85eb6BB1989E2368362 ](https://explore.tempo.xyz/address/0xAE863452f44ADD237739A85eb6BB1989E2368362?tab=contract) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x40755e3e2513D71cB79DC3Eeefd8Eb848d9cd899 ](https://uniscan.xyz/address/0x40755e3e2513D71cB79DC3Eeefd8Eb848d9cd899) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0x0628B860947fA0c195988F65d53850546A489732 ](https://uniscan.xyz/address/0x0628B860947fA0c195988F65d53850546A489732) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0x2c4331BC5EC245da744F35832C8797456cFC8045 ](https://uniscan.xyz/address/0x2c4331BC5EC245da744F35832C8797456cFC8045#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | | Contract | Address | Source Code | | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | VaultBundlesV1 | [ 0x768BBD5F6c22b2498cC9C19832d3AEE08240755a ](https://worldscan.org/address/0x768BBD5F6c22b2498cC9C19832d3AEE08240755a) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault/VaultBundlesV1.sol) | | VaultExitBundlesV1 | [ 0xcf7b4a40f25A6b839A93b8A8b45297F2a5383E73 ](https://worldscan.org/address/0xcf7b4a40f25A6b839A93b8A8b45297F2a5383E73) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/vault-exit/VaultExitBundlesV1.sol) | | BlueBundlesV1 | [ 0xEd44094c917D891D68f46E8cc2fd50D790707a65 ](https://worldscan.org/address/0xEd44094c917D891D68f46E8cc2fd50D790707a65#code) | [morpho-org/bundles](https://github.com/morpho-org/bundles/blob/main/src/blue/BlueBundlesV1.sol) | ## Bundlers [#bundlers] | Contract | Address | Source Code | | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x6566194141eefa99Af43Bb5Aa71460Ca2Dc90245 ](https://etherscan.io/address/0x6566194141eefa99Af43Bb5Aa71460Ca2Dc90245#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | EthereumGeneralAdapter1 | [ 0x4A6c312ec70E8747a587EE860a0353cd42Be0aE0 ](https://etherscan.io/address/0x4A6c312ec70E8747a587EE860a0353cd42Be0aE0#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ERC20WrapperAdapter | [ 0xf83D17dFE160597b19e4FdD8ea61A23e9a87F962 ](https://etherscan.io/address/0xf83D17dFE160597b19e4FdD8ea61A23e9a87F962#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0x03b5259Bd204BfD4A616E5B79b0B786d90c6C38f ](https://etherscan.io/address/0x03b5259Bd204BfD4A616E5B79b0B786d90c6C38f#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV3MigrationAdapter “Core” | [ 0xb09e40EbE31b738fbf20289270a397118707D475 ](https://etherscan.io/address/0xb09e40EbE31b738fbf20289270a397118707D475#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV3MigrationAdapter “Prime” | [ 0x2CC8d502a65824B4cF9A58DB03490bA024BDB806 ](https://etherscan.io/address/0x2CC8d502a65824B4cF9A58DB03490bA024BDB806#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV3MigrationAdapter “EtherFi” | [ 0x4011dc6581fA05F9B0c7A12AdCd676e2b1a59ca3 ](https://etherscan.io/address/0x4011dc6581fA05F9B0c7A12AdCd676e2b1a59ca3#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | CompoundV3MigrationAdapter | [ 0xdBa5bdE29eA030Bfa6A608592dFcA1D02CB26773 ](https://etherscan.io/address/0xdBa5bdE29eA030Bfa6A608592dFcA1D02CB26773#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV3OptimizerMigrationAdapter | [ 0x9e2ea2d5785598a163D569D795f286F5C55ad972 ](https://etherscan.io/address/0x9e2ea2d5785598a163D569D795f286F5C55ad972#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV2MigrationAdapter | [ 0x40288815C399709dFC0875A384B637fFe387961B ](https://etherscan.io/address/0x40288815C399709dFC0875A384B637fFe387961B#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | CompoundV2MigrationAdapter | [ 0x9B89c07f480Df1945279031b5fC6fF241b8f1101 ](https://etherscan.io/address/0x9B89c07f480Df1945279031b5fC6fF241b8f1101#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xc13A3Ca3B0120EE6121d1E0ca9Da22fDD7ed28Cd ](https://abscan.org/address/0xc13A3Ca3B0120EE6121d1E0ca9Da22fDD7ed28Cd#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x3716AA06304D1bC70f553Da44904d13086A4a791 ](https://abscan.org/address/0x3716AA06304D1bC70f553Da44904d13086A4a791#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x1FA4431bC113D308beE1d46B0e98Cb805FB48C13 ](https://arbiscan.io/address/0x1FA4431bC113D308beE1d46B0e98Cb805FB48C13#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x9954aFB60BB5A222714c478ac86990F221788B88 ](https://arbiscan.io/address/0x9954aFB60BB5A222714c478ac86990F221788B88#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0xAA5c30C1482c189cA0d56057D3ac4dD7Af1e4726 ](https://arbiscan.io/address/0xAA5c30C1482c189cA0d56057D3ac4dD7Af1e4726#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV3MigrationAdapter | [ 0x1923670d4F4eB7435d865E7477d28FEAFfA40C93 ](https://arbiscan.io/address/0x1923670d4F4eB7435d865E7477d28FEAFfA40C93#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | CompoundV3MigrationAdapter | [ 0x86Ca77a4a37A9CDBe9bBf4975F6d69531B96444b ](https://arbiscan.io/address/0x86Ca77a4a37A9CDBe9bBf4975F6d69531B96444b#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | 0x855dbe13C409Df75CAF6a985Cf6993a4D0319Feb | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | 0x0AF5BE7BD7B615e94e1d9275414482D028952466 | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x708e04d46D82d8f1D0A70a3Aa0a780F27fcb3F92 ](https://snowtrace.io/address/0x708e04d46D82d8f1D0A70a3Aa0a780F27fcb3F92/contract/43114/code?chainid=43114) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x8BcC016Ab7334A16DCcbEE9AB3FD6E52fEb29E96 ](https://snowtrace.io/address/0x8BcC016Ab7334A16DCcbEE9AB3FD6E52fEb29E96/contract/43114/code?chainid=43114) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0x0ED0478946E00ca8BCF65475E7c8dfACA4a30005 ](https://snowtrace.io/address/0x0ED0478946E00ca8BCF65475E7c8dfACA4a30005/contract/43114/code?chainid=43114) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x6BFd8137e702540E7A42B74178A4a49Ba43920C4 ](https://basescan.org/address/0x6BFd8137e702540E7A42B74178A4a49Ba43920C4#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xb98c948CFA24072e58935BC004a8A7b376AE746A ](https://basescan.org/address/0xb98c948CFA24072e58935BC004a8A7b376AE746A#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ERC20WrapperAdapter | [ 0xdeEf55F0A7366cC3Baf5E04313269389Fe17E9AE ](https://basescan.org/address/0xdeEf55F0A7366cC3Baf5E04313269389Fe17E9AE#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0x6abE8ABd0275E5564ed1336F0243A52C32562F71 ](https://basescan.org/address/0x6abE8ABd0275E5564ed1336F0243A52C32562F71#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | CompoundV3MigrationAdapter | [ 0x85D4812Ef92c040d4270eD8547b6835e41FbbB70 ](https://basescan.org/address/0x85D4812Ef92c040d4270eD8547b6835e41FbbB70#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | AaveV3MigrationAdapter | [ 0xb27Aa2a964eAd5ed661D86974b37e4fB995b36f5 ](https://basescan.org/address/0xb27Aa2a964eAd5ed661D86974b37e4fB995b36f5#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x82b3ea7558Fc383b949fa42Db7ee3eB101447B96 ](https://www.btrscan.com/address/0x82b3ea7558fc383b949fa42db7ee3eb101447b96?tab=Contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xa87F1422Df88B5f490203D71e2f8e7244843E62F ](https://www.btrscan.com/address/0xa87f1422df88b5f490203d71e2f8e7244843e62f?tab=Contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x16D40b9DF1497468195BFAfeb2718e486E15bF91 ](https://bscscan.com/address/0x16D40b9DF1497468195BFAfeb2718e486E15bF91#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x87c93660ECe6E68C6492EabBbBdbaafA102ae3a3 ](https://bscscan.com/address/0x87c93660ECe6E68C6492EabBbBdbaafA102ae3a3#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0xBb12B012Fa31f7FE418236cAf625713Edc852F82 ](https://bscscan.com/address/0xBb12B012Fa31f7FE418236cAf625713Edc852F82#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x88A4038BB4A14ecD7301c8Fb1f1e8732c4355D53 ](https://camp.cloud.blockscout.com/address/0x88A4038BB4A14ecD7301c8Fb1f1e8732c4355D53?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x29dcA26F9862CFb8064163ddc3401aaB4D4D05c6 ](https://camp.cloud.blockscout.com/address/0x29dcA26F9862CFb8064163ddc3401aaB4D4D05c6?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xbd142f98f847c170D51d8B23e5FEBc51FC9a67D9 ](https://celoscan.io/address/0xbd142f98f847c170D51d8B23e5FEBc51FC9a67D9#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x3E7544a07157D03a49359eE89f2fCac9a6467230 ](https://celoscan.io/address/0x3E7544a07157D03a49359eE89f2fCac9a6467230#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xe7C7271AfE4E49C12c66a0B8A9B774ed8b1640C8 ](https://explorer.mainnet.citrea.xyz/address/0xe7C7271AfE4E49C12c66a0B8A9B774ed8b1640C8?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x722B0e321544042741B3E45F200FBEDAc20529dC ](https://explorer.mainnet.citrea.xyz/address/0x722B0e321544042741B3E45F200FBEDAc20529dC?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xb1c59fE6A0DCE25b804F6113C441Bf4F3a4Ab6bC ](https://explorer.cronos.org/address/0xb1c59fE6A0DCE25b804F6113C441Bf4F3a4Ab6bC#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xCa470cBBc3de56FDe336466f2107cC969174d513 ](https://explorer.cronos.org/address/0xCa470cBBc3de56FDe336466f2107cC969174d513#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x4D403d6a3D25A865Ed57caef884f076E0fa00eCa ](https://eden.blockscout.com/address/0x4D403d6a3D25A865Ed57caef884f076E0fa00eCa?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x9B05CCA3299E3558a324f72aaB4D404625557B3D ](https://eden.blockscout.com/address/0x9B05CCA3299E3558a324f72aaB4D404625557B3D?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x69dc8086191437b55775b79C730BB3876397e7D1 ](https://explorer.etherlink.com/address/0x69dc8086191437b55775b79C730BB3876397e7D1?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xEabdAC78A7f0a9B3dF0e23D69A5a5fF7f580a910 ](https://explorer.etherlink.com/address/0xEabdAC78A7f0a9B3dF0e23D69A5a5fF7f580a910?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xb371CFee69984D1F3447D49217889C7388bA2027 ](https://flare-explorer.flare.network/address/0xb371CFee69984D1F3447D49217889C7388bA2027?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x43F4861c9E7f584cFDc9B6BA3F0c2462895BDd67 ](https://flare-explorer.flare.network/address/0x43F4861c9E7f584cFDc9B6BA3F0c2462895BDd67?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xA7a414823Ef0F8CFb2c4f67f2F445DA940641d91 ](https://fraxscan.com/address/0xA7a414823Ef0F8CFb2c4f67f2F445DA940641d91) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x228dDF333DDf6D1895dA1dE8a846EDD27F1284eD ](https://fraxscan.com/address/0x228dDF333DDf6D1895dA1dE8a846EDD27F1284eD) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xE09314FE5Fc41FEc8f3e6042085dD3CeE24c877c ](https://gensyn-mainnet.explorer.alchemy.com/address/0xE09314FE5Fc41FEc8f3e6042085dD3CeE24c877c?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x79e6825ccb881d276f988a5dA8125e1c6BEa07Ae ](https://gensyn-mainnet.explorer.alchemy.com/address/0x79e6825ccb881d276f988a5dA8125e1c6BEa07Ae?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x2AC3ea771547926D4714078e807eFbeF70D0997f ](https://gnosis.blockscout.com/address/0x2AC3ea771547926D4714078e807eFbeF70D0997f?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x832625F5C0aAD4bc14d39291156D37898a40973b ](https://gnosis.blockscout.com/address/0x832625F5C0aAD4bc14d39291156D37898a40973b?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x8eDa6E01a20E3Cd90B3B2AF6F790cB8FADEf3Ea8 ](https://explorer.hemi.xyz/address/0x8eDa6E01a20E3Cd90B3B2AF6F790cB8FADEf3Ea8) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x9623090C3943ad63F7d794378273610Dd0deeFD4 ](https://explorer.hemi.xyz/address/0x9623090C3943ad63F7d794378273610Dd0deeFD4) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xa3F50477AfA601C771874260A3B34B40e244Fa0e ](https://hyperevmscan.io/address/0xa3F50477AfA601C771874260A3B34B40e244Fa0e#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xD7F48aDE56613E8605863832B7B8A1985B934aE4 ](https://hyperevmscan.io/address/0xD7F48aDE56613E8605863832B7B8A1985B934aE4#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x7db0F1E2bf1f47ec82220090F388d75D8B9BB6BC ](https://explorer.inkonchain.com/address/0x7db0F1E2bf1f47ec82220090F388d75D8B9BB6BC) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xB8B2aDdCDe1cdC94AaE18a0F8A19df03D8683610 ](https://explorer.inkonchain.com/address/0xB8B2aDdCDe1cdC94AaE18a0F8A19df03D8683610) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x27880B18ae04a05F1D603B87AEb2a27491FfaBA9 ](https://kaiascan.io/address/0x27880B18ae04a05F1D603B87AEb2a27491FfaBA9?tabId=contract\&page=1) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x8e36C2c6d7771820BF14a75f725f3cf0374a7823 ](https://kaiascan.io/address/0x8e36C2c6d7771820BF14a75f725f3cf0374a7823?tabId=contract\&page=1) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xA8C5e23C9C0DF2b6fF716486c6bBEBB6661548C8 ](https://katanascan.com/address/0xA8C5e23C9C0DF2b6fF716486c6bBEBB6661548C8#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x916Aa175C36E845db45fF6DDB886AE437d403B61 ](https://katanascan.com/address/0x916Aa175C36E845db45fF6DDB886AE437d403B61#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x1Ee8Ec299E8014760D50A4E3CfC3b44Cc2242625 ](https://lineascan.build/address/0x1Ee8Ec299E8014760D50A4E3CfC3b44Cc2242625#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x454dAb6ce9891245696b239b4845a1cDC268255d ](https://lineascan.build/address/0x454dAb6ce9891245696b239b4845a1cDC268255d#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xD96E5e02580C4EAfE15B5537b25eE3dEe5861e00 ](https://blockscout.lisk.com/address/0xD96E5e02580C4EAfE15B5537b25eE3dEe5861e00?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x76cFE4BF840C7b461772fE7CDE399f58c4173584 ](https://blockscout.lisk.com/address/0x76cFE4BF840C7b461772fE7CDE399f58c4173584?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xf53D4c8f0f83F697CD6bB303567400cCf411aA63 ](https://megaeth.blockscout.com/address/0xf53D4c8f0f83F697CD6bB303567400cCf411aA63?tab=contract_code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x74d3cbc721613C8461df92658d0a20dF275Ca31b ](https://megaeth.blockscout.com/address/0x74d3cbc721613C8461df92658d0a20dF275Ca31b?tab=contract_code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xFEA0edFa081C8D5960Ec9Bf6684981dB1834305d ](https://explorer.mode.network/address/0xFEA0edFa081C8D5960Ec9Bf6684981dB1834305d) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xF53925b95Cc409447066cd5c1A7756084b2Ee0a4 ](https://explorer.mode.network/address/0xF53925b95Cc409447066cd5c1A7756084b2Ee0a4) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [0x82b684483e844422FD339df0b67b3B111F02c66E](https://monadscan.com/address/0x82b684483e844422FD339df0b67b3B111F02c66E#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [0x725AB8CAd931BCb80Fdbf10955a806765cCe00e5](https://monadscan.com/address/0x725AB8CAd931BCb80Fdbf10955a806765cCe00e5#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xDB83A44Bf8f53966f7A4F7dCE7F8CdAaBCf83628 ](https://explorer.morph.network/address/0xDB83A44Bf8f53966f7A4F7dCE7F8CdAaBCf83628?tab=contract_code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xcaeec65c85Fe964c8Bd814cb8E4CaF8B06bde776 ](https://explorer.morph.network/address/0xcaeec65c85Fe964c8Bd814cb8E4CaF8B06bde776?tab=contract_code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xFBCd3C258feB131D8E038F2A3a670A7bE0507C05 ](https://optimistic.etherscan.io/address/0xFBCd3C258feB131D8E038F2A3a670A7bE0507C05) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x79481C87f24A3C4332442A2E9faaf675e5F141f0 ](https://optimistic.etherscan.io/address/0x79481C87f24A3C4332442A2E9faaf675e5F141f0) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0x31F539f4Ed14fA1fd18781e93f6739249692aDC5 ](https://optimistic.etherscan.io/address/0x31F539f4Ed14fA1fd18781e93f6739249692aDC5) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | 0x3c90c09F8c5d927a117F681fB924952DbbD99120 | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | 0x0A5819708bCAD9C716dFD58ecB58cF91399A53f8 | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xeC6f72cF30fb5d3D0f9Bea8509B61902Cf3B1E45 ](https://plasmascan.to/address/0xeC6f72cF30fb5d3D0f9Bea8509B61902Cf3B1E45/contract/9745/code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x6A7389f9D4fD199254479d6735aAd7aa5D4Db0cE ](https://plasmascan.to/address/0x6A7389f9D4fD199254479d6735aAd7aa5D4Db0cE/contract/9745/code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x5437C8788f4CFbaA55be6FBf30379bc7dd7f69C3 ](https://explorer.plume.org/address/0x5437C8788f4CFbaA55be6FBf30379bc7dd7f69C3?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x65ff368930Cb7eB4CA5C5eBC58bb69E6Ed198BA5 ](https://explorer.plume.org/address/0x65ff368930Cb7eB4CA5C5eBC58bb69E6Ed198BA5?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x2d9C3A9E67c966C711208cc78b34fB9E9f8db589 ](https://polygonscan.com/address/0x2d9C3A9E67c966C711208cc78b34fB9E9f8db589) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xB261B51938A9767406ef83bbFbaAFE16691b7047 ](https://polygonscan.com/address/0xB261B51938A9767406ef83bbFbaAFE16691b7047) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0x5F2617F12D1fDd1e43e72Cb80C92dFcE8124Db8d ](https://polygonscan.com/address/0x5F2617F12D1fDd1e43e72Cb80C92dFcE8124Db8d) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x31A77d9c1Fa2c8d45170E7048F46d7B06C9B5BB4 ](https://explorer.risechain.com/address/0x31A77d9c1Fa2c8d45170E7048F46d7B06C9B5BB4?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x24E07727aa13b9A330CB73c65958dE4C3A667408 ](https://explorer.risechain.com/address/0x24E07727aa13b9A330CB73c65958dE4C3A667408?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x6478e9393d4C5bB4d53ee881d1DE78786A0344a6 ](https://robinhoodchain.blockscout.com/address/0x6478e9393d4C5bB4d53ee881d1DE78786A0344a6?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xc5E188541D107e8B79e43478bDE365F1406665D6 ](https://robinhoodchain.blockscout.com/address/0xc5E188541D107e8B79e43478bDE365F1406665D6?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x60F9159d4dCd724e743212416FD57d8aC0B60768 ](https://scrollscan.com/address/0x60F9159d4dCd724e743212416FD57d8aC0B60768) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xD2780fae0869cDc06EE202152304A39653361525 ](https://scrollscan.com/address/0xD2780fae0869cDc06EE202152304A39653361525) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xF9457356F18A3349Bb317Ac144c3Bcc62e5761aD ](https://seiscan.io/address/0xF9457356F18A3349Bb317Ac144c3Bcc62e5761aD#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x02e0e71e145f254820B9D89c9E6068f08256F601 ](https://seiscan.io/address/0x02e0e71e145f254820B9D89c9E6068f08256F601#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x461378B79d400c963F48F57b3a99416bc3C5c6a6 ](https://soneium.blockscout.com/address/0x461378B79d400c963F48F57b3a99416bc3C5c6a6?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xA47EeDE3Aac741B830E394B2e291f6774BD8bb48 ](https://soneium.blockscout.com/address/0xA47EeDE3Aac741B830E394B2e291f6774BD8bb48?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xB06F1Ad8c908b958E596c42973f67F2f1d9a9afF ](https://sonicscan.org/address/0xB06F1Ad8c908b958E596c42973f67F2f1d9a9afF) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x31D5aee8D75EEab548cfA0d11C4f9843a5201eaf ](https://sonicscan.org/address/0x31D5aee8D75EEab548cfA0d11C4f9843a5201eaf) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xA0bb114F927dF03d9a1a639b9c71F71B0FaFDf1B ](https://stablescan.xyz/address/0xA0bb114F927dF03d9a1a639b9c71F71B0FaFDf1B#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x59b1F4376a81e39c466A0A218447E4D36f39A96b ](https://stablescan.xyz/address/0x59b1F4376a81e39c466A0A218447E4D36f39A96b#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x84b189823D0f84c36728Bb3335dD8C833564e72f ](https://explorer.tac.build/address/0x84b189823D0f84c36728Bb3335dD8C833564e72f?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x6D94E7dCA6d8FAE2CF954633C2Cf9c286258E0af ](https://explorer.tac.build/address/0x6D94E7dCA6d8FAE2CF954633C2Cf9c286258E0af?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x5A3B829dcDBE1979cdD9F648Afb6D39dCF805E33 ](https://explore.tempo.xyz/address/0x5A3B829dcDBE1979cdD9F648Afb6D39dCF805E33?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x3a2A86bD16629B054f7f3d87F0a7570F3881a43c ](https://explore.tempo.xyz/address/0x3a2A86bD16629B054f7f3d87F0a7570F3881a43c?tab=contract) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x7DD85759182495AF7F6757DA75036d24A9B58bc3 ](https://uniscan.xyz/address/0x7dd85759182495af7f6757da75036d24a9b58bc3#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xC11329d19C2275c9E759867e879ECFcEeD7e30A0 ](https://uniscan.xyz/address/0xC11329d19C2275c9E759867e879ECFcEeD7e30A0#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | ParaswapAdapter | [ 0xAa870Da2a9F611A3A53d0D2AEe5664B3700a59c9 ](https://uniscan.xyz/address/0xAa870Da2a9F611A3A53d0D2AEe5664B3700a59c9#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | CompoundV3MigrationAdapter | [ 0x617f8d7885CCE689115Af04576F7cB6F2534fA9a ](https://uniscan.xyz/address/0x617f8d7885CCE689115Af04576F7cB6F2534fA9a#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x3D07BF2FFb23248034bF704F3a4786F1ffE2a448 ](https://worldscan.org/address/0x3D07BF2FFb23248034bF704F3a4786F1ffE2a448) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x30fa9A3cF56931ACEea42E28D35519a97D90aA67 ](https://worldscan.org/address/0x30fa9A3cF56931ACEea42E28D35519a97D90aA67) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0xed9bdc3E6081db528b6D5CDDf47EcB05337c62A7 ](https://xdcscan.com/address/0xed9bdc3E6081db528b6D5CDDf47EcB05337c62A7#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0xAB2Ab6A8bb1082C5d8400D6206c6A13cE413e0c0 ](https://xdcscan.com/address/0xAB2Ab6A8bb1082C5d8400D6206c6A13cE413e0c0#code) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | Contract | Address | Source Code | | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | | Bundler3 | [ 0x04C17BA8BAe185e655086e7f35357F7c3C70Dc53 ](https://explorer.zircuit.com/address/0x04C17BA8BAe185e655086e7f35357F7c3C70Dc53?activeTab=3) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | | GeneralAdapter1 | [ 0x2d09A9A5d41863314571E3c902D1b9DDfe6cAf9e ](https://explorer.zircuit.com/address/0x2d09A9A5d41863314571E3c902D1b9DDfe6cAf9e?activeTab=3) | [morpho-org/bundler3](https://github.com/morpho-org/bundler3/tree/82e44ddebae998dc8b6e5cbd29ff69786135b1d3) | ## Rewards [#rewards] | Contract | Address | Source Code | | :---------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x9baA51245CDD28D8D74Afe8B3959b616E9ee7c8D ](https://etherscan.io/address/0x9baA51245CDD28D8D74Afe8B3959b616E9ee7c8D#code) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Universal Rewards Distributor (used with the Market Rewards Program Registry) | [ 0x330eefa8a787552DC5cAd3C3cA644844B1E61Ddb ](https://etherscan.io/address/0x330eefa8a787552DC5cAd3C3cA644844B1E61Ddb#code) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UniversalRewardsDistributor.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x7b792Ef7e91fbc78Ef482E3bBB52193A73367fbf ](https://arbiscan.io/address/0x7b792Ef7e91fbc78Ef482E3bBB52193A73367fbf) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :---------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x7276454fc1cf9C408deeed722fd6b5E7A4CA25D8 ](https://basescan.org/address/0x7276454fc1cf9C408deeed722fd6b5E7A4CA25D8#code) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Universal Rewards Distributor (used with the Market Rewards Program Registry) | [ 0x5400dBb270c956E8985184335A1C62AcA6Ce1333 ](https://basescan.org/address/0x5400dBb270c956E8985184335A1C62AcA6Ce1333#code) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UniversalRewardsDistributor.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0xeb8AA0388b03c74FA48E78f56543e7b8d1965eaf ](https://fraxscan.com/address/0xeb8AA0388b03c74FA48E78f56543e7b8d1965eaf) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x184e0A5a6e52b30B7139D2C02FEE11516B8a8562 ](https://explorer.hemi.xyz/address/0x184e0A5a6e52b30B7139D2C02FEE11516B8a8562) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x2884732D25b97090CaB59d0d2B78cD9c85A83411 ](https://explorer.inkonchain.com/address/0x2884732D25b97090CaB59d0d2B78cD9c85A83411) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0xB605Ae0D112c117638592ec4F78148e6322a7b7b ](https://katanascan.com/address/0xB605Ae0D112c117638592ec4F78148e6322a7b7b#code) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0xC3da6acf214e322Cc0E07D1582c9a546fB4338da ](https://blockscout.lisk.com/address/0xC3da6acf214e322Cc0E07D1582c9a546fB4338da?tab=contract) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x063A8f498B4A1423c62Be360007B7d9d0C60c481 ](https://explorer.mode.network/address/0x063A8f498B4A1423c62Be360007B7d9d0C60c481) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0xe41AEcB4570A7B68d15a4Fb0a03ACEe421A21498 ](https://optimistic.etherscan.io/address/0xe41AEcB4570A7B68d15a4Fb0a03ACEe421A21498) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x92428E3906a1e7F4ecEBaF88949f76a6FB32cC78 ](https://explorer.plume.org/address/0x92428E3906a1e7F4ecEBaF88949f76a6FB32cC78?tab=contract) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0xfc5BE756236Feaf4297d3279Bf6532bf100f0AC9 ](https://polygonscan.com/address/0xfc5BE756236Feaf4297d3279Bf6532bf100f0AC9) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x78F44e1ed61210952B6e23d09C930BfA64a359c1 ](https://scrollscan.com/address/0x78F44e1ed61210952B6e23d09C930BfA64a359c1) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x90D441D6B232Eb30Edb74256995fB9bcb59AD63a ](https://soneium.blockscout.com/address/0x90D441D6B232Eb30Edb74256995fB9bcb59AD63a?tab=contract) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0xdFA21A9189425187fF6e44D7ebeA19F64E8836ee ](https://sonicscan.org/address/0xdFA21A9189425187fF6e44D7ebeA19F64E8836ee) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x72cc7A247f389D327bC898420837477fb4a57Cc4 ](https://uniscan.xyz/address/0x72cc7A247f389D327bC898420837477fb4a57Cc4) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | | Contract | Address | Source Code | | :------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | | Universal Rewards Distributor Factory | [ 0x02C895e99Db5348284B743512CF32a016f76286B ](https://worldscan.org/address/0x02C895e99Db5348284B743512CF32a016f76286B) | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor/blob/v1.0.0/src/UrdFactory.sol) | ## MORPHO Token [#morpho-token] The MORPHO token has 18 decimals. | Contract | Address | Source Code | | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------- | | MORPHO | [ 0x58D97B57BB95320F9a05dC918Aef65434969c2B2 ](https://etherscan.io/address/0x58D97B57BB95320F9a05dC918Aef65434969c2B2#code) | [morpho-org/morpho-token](https://github.com/morpho-org/morpho-token) | | MorphoOFTAdapter | [ 0x50d3d6fD7518682155E3C1B65FDD50e1b35649D9 ](https://etherscan.io/address/0x50d3d6fD7518682155E3C1B65FDD50e1b35649D9#code) | [morpho-org/morpho-token-l0](https://github.com/morpho-org/morpho-token-l0) | | MORPHO wrapper | [ 0x9D03bb2092270648d7480049d0E58d2FcF0E5123 ](https://etherscan.io/address/0x9d03bb2092270648d7480049d0e58d2fcf0e5123#code) | [morpho-org/morpho-token](https://github.com/morpho-org/morpho-token) | | Legacy MORPHO | [ 0x9994E35Db50125E0DF82e4c2dde62496CE330999 ](https://etherscan.io/address/0x9994E35Db50125E0DF82e4c2dde62496CE330999#code) | [morpho-org/legacy-morpho-token](https://github.com/morpho-org/legacy-morpho-token) | | Contract | Address | Source Code | | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------- | | MORPHO | [ 0xBAa5CC21fd487B8Fcc2F632f3F4E8D37262a0842 ](https://basescan.org/address/0xBAa5CC21fd487B8Fcc2F632f3F4E8D37262a0842#code) | [morpho-org/morpho-token](https://github.com/morpho-org/morpho-token) | | Contract | Address | Source Code | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------- | | MORPHO | [ 0x40BD670A58238e6E230c430BBb5cE6ec0d40df48 ](https://arbiscan.io/address/0x40bd670a58238e6e230c430bbb5ce6ec0d40df48) | [morpho-org/morpho-token-l0](https://github.com/morpho-org/morpho-token-l0) | | MorphoMintBurnOFTAdapter | [ 0xFc3329363cd51adBBaa52E389bEE389981ccaAE0 ](https://arbiscan.io/address/0xFc3329363cd51adBBaa52E389bEE389981ccaAE0) | [morpho-org/morpho-token-l0](https://github.com/morpho-org/morpho-token-l0) | | Contract | Address | Source Code | | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | MORPHO | [ 0x1e5eFCA3D0dB2c6d5C67a4491845c43253eB9e4e ](https://explorer.katanarpc.com/token/0x1e5eFCA3D0dB2c6d5C67a4491845c43253eB9e4e?tab=contract_code) | [agglayer/TokenWrappedBridgeUpgradeable](https://github.com/agglayer/agglayer-contracts/blob/feature/update-sovereign-L2/contracts/v2/lib/TokenWrappedBridgeUpgradeable.sol) | --- # Blue Source: https://docs.morpho.org/developers/contracts/blue This page documents the Blue (Morpho Blue) variable rate market contract. ## Market Parameters [#market-parameters] ### Market id [#market-id] The market id is a `bytes32` keccak256 hash of the [5 parameters of a market](#marketparams-struct). It is used to identify a market in Morpho. ```solidity type Id is bytes32; ``` To get the id from the parameters, a temporary solution is to execute [this gist](https://gist.github.com/tomrpl/3cfd34e04a01f9cbae2b16887f8026cf), or retrieve the id at market creation. ### Market struct [#market-struct] The market struct is defined as follows: ```solidity struct Market { uint128 totalSupplyAssets; uint128 totalSupplyShares; uint128 totalBorrowAssets; uint128 totalBorrowShares; uint128 lastUpdate; uint128 fee; } ``` It contains the state of a market. `totalBorrowAssets` & `totalSupplyAssets` accrues the interest of the market only until last interest accrual, while `totalBorrowShares` & `totalSupplyShares` are representing the total shares distributed over lenders / borrowers. ### MarketParams struct [#marketparams-struct] The majority of the entrypoints accept a `MarketParams` struct as parameter. This struct is defined as follows: ```solidity struct MarketParams { address loanToken; address collateralToken; address oracle; address irm; uint256 lltv; } ``` As an integrator, you need to recover these 5 parameters to interact with Morpho. You can also use the [idToMarketParams](#idtomarketparams) view function to recover the parameters of a given market from the bytes32 id. ## Functions [#functions] ### setOwner [#setowner] ```solidity function setOwner(address newOwner) external; ``` Sets `newOwner` as `owner` of the contract. * Warning: No two-step transfer ownership. * Warning: The owner can be set to the zero address. #### Parameters: [#parameters] | Name | Type | Description | | :--------- | :------ | :--------------------- | | `newOwner` | address | The new owner address. | ### enableIrm [#enableirm] ```solidity function enableIrm(address irm) external; ``` Enables `irm` as a possible IRM for market creation. * Warning: It is not possible to disable an IRM. #### Parameters: [#parameters-1] | Name | Type | Description | | :---- | :------ | :------------------------- | | `irm` | address | The irm address to enable. | ### enableLltv [#enablelltv] ```solidity function enableLltv(uint256 lltv) external; ``` Enables `lltv` as a possible LLTV for market creation. * Warning: It is not possible to disable a LLTV. #### Parameters: [#parameters-2] | Name | Type | Description | | :----- | :------ | :-------------------------------------- | | `lltv` | uint256 | The lltv value to enable (18 decimals). | ### setFee [#setfee] ```solidity function setFee(MarketParams memory marketParams, uint256 newFee) external; ``` Sets the `newFee` for the given market `marketParams`. * Warning: The recipient can be the zero address. #### Parameters: [#parameters-3] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market parameters. | | `newFee` | uint256 | The new fee value. | ### setFeeRecipient [#setfeerecipient] ```solidity function setFeeRecipient(address newFeeRecipient) external; ``` Sets `newFeeRecipient` as `feeRecipient` of the fee. * Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost. * Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes. #### Parameters: [#parameters-4] | Name | Type | Description | | :---------------- | :------ | :------------------------------------ | | `newFeeRecipient` | address | The address of the new fee recipient. | ### createMarket [#createmarket] ```solidity function createMarket(MarketParams memory marketParams) external; ``` Creates the market defined by `marketParams`. Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees Morpho behaves as expected: * The token should be ERC20 compliant, except that it can omit return values on `transfer` and `transferFrom`. * The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with burn functions are not supported. * The token should not re-enter Morpho on `transfer` nor `transferFrom`. * The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported. * The IRM should not re-enter Morpho. * The oracle should return a price with the correct scaling. Here is a list of properties on the market's dependencies that could break Morpho's liveness properties: * The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue. * A very high amount of assets (\~1e35) supplied or borrowed can make the computation of `toSharesUp` and `toSharesDown` overflow. * The IRM can revert on `borrowRate`. * A very high borrow rate returned by the IRM can make the computation of `interest` in `_accrueInterest` overflow. * The oracle can revert on `price`. Note that this can be used to prevent `borrow`, `withdrawCollateral` and `liquidate` from being used under certain market conditions. * A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or the computation of `assetsRepaid` in `liquidate` overflow. * The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to the point where `totalBorrowShares` is very large and borrowing overflows. #### Parameters: [#parameters-5] | Name | Type | Description | | :------------- | :----------------------------------- | :-------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market parameters | ### supply [#supply] ```solidity function supply( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, bytes memory data ) external returns (uint256 assetsSupplied, uint256 sharesSupplied); ``` Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's `onMorphoSupply` function with the given `data`. * Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific amount of shares is given for full compatibility and precision. * Supplying a large amount can revert for overflow. * Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage. Consider using the `assets` parameter to avoid this. #### Parameters: [#parameters-6] | Name | Type | Description | | :------------- | :----------------------------------- | :-------------------------------------------------------------------------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market to supply assets to. | | `assets` | uint256 | The amount of assets to supply. | | `shares` | uint256 | The amount of shares to mint. | | `onBehalf` | address | The address that will own the increased supply position. | | `data` | bytes | Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed. | #### Return Values: [#return-values] | Name | Type | Description | | :--------------- | :------ | :----------------------------- | | `assetsSupplied` | uint256 | The amount of assets supplied. | | `sharesSupplied` | uint256 | The amount of shares minted. | ### withdraw [#withdraw] ```solidity function withdraw( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver ) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn); ``` Withdraws `assets` or `shares` on behalf of `onBehalf` to `receiver`. * Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`. * `msg.sender` must be authorized to manage `onBehalf`'s positions. * Withdrawing an amount corresponding to more shares than supplied will revert for underflow. * It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to conversion roundings between `shares` and `assets`. #### Parameters: [#parameters-7] | Name | Type | Description | | :------------- | :----------------------------------- | :-------------------------------------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market to withdraw assets from. | | `assets` | uint256 | The amount of assets to withdraw. | | `shares` | uint256 | The amount of shares to burn. | | `onBehalf` | address | The address of the owner of the supply position. | | `receiver` | address | The address that will receive the withdrawn assets. | #### Return Values: [#return-values-1] | Name | Type | Description | | :---------------- | :------ | :------------------------------ | | `assetsWithdrawn` | uint256 | The amount of assets withdrawn. | | `sharesWithdrawn` | uint256 | The amount of shares burned. | ### borrow [#borrow] ```solidity function borrow( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, address receiver ) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed); ``` Borrows `assets` or `shares` on behalf of `onBehalf` to `receiver`. * Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is given for full compatibility and precision. * `msg.sender` must be authorized to manage `onBehalf`'s positions. * Borrowing a large amount can revert for overflow. * Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage. Consider using the `assets` parameter to avoid this. #### Parameters: [#parameters-8] | Name | Type | Description | | :------------- | :----------------------------------- | :------------------------------------------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market to borrow assets from. | | `assets` | uint256 | The amount of assets to borrow. | | `shares` | uint256 | The amount of shares to mint. | | `onBehalf` | address | The address that will own the increased borrow position. | | `receiver` | address | The address that will receive the borrowed assets. | #### Return Values: [#return-values-2] | Name | Type | Description | | :--------------- | :------ | :----------------------------- | | `assetsBorrowed` | uint256 | The amount of assets borrowed. | | `sharesBorrowed` | uint256 | The amount of shares minted. | ### repay [#repay] ```solidity function repay( MarketParams memory marketParams, uint256 assets, uint256 shares, address onBehalf, bytes memory data ) external returns (uint256 assetsRepaid, uint256 sharesRepaid); ``` Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's `onMorphoRepay` function with the given `data`. * Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`. * Repaying an amount corresponding to more shares than borrowed will revert for underflow. * It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion roundings between shares and assets. * An attacker can front-run a repay with a small repay making the transaction revert for underflow. #### Parameters: [#parameters-9] | Name | Type | Description | | :------------- | :----------------------------------- | :---------------------------------------------------------------------------------------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market to repay assets from. | | `assets` | uint256 | The amount of assets to repay. | | `shares` | uint256 | The amount of shares to burn. | | `onBehalf` | address | The address of the owner of the debt position. | | `data` | bytes | Arbitrary data (example `0x`) to pass to the `onMorphoRepay` callback. Pass empty data if not needed. | #### Return Values: [#return-values-3] | Name | Type | Description | | :------------- | :------ | :--------------------------- | | `assetsRepaid` | uint256 | The amount of assets repaid. | | `sharesRepaid` | uint256 | The amount of shares burned. | ### supplyCollateral [#supplycollateral] ```solidity function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data) external; ``` Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's `onMorphoSupplyCollateral` function with the given `data`. * Interest are not accrued since it's not required and it saves gas. * Supplying a large amount can revert for overflow. #### Parameters: [#parameters-10] | Name | Type | Description | | :------------- | :----------------------------------- | :------------------------------------------------------------------------------------------------ | | `marketParams` | [MarketParams](#marketparams-struct) | The market to supply collateral to. | | `assets` | uint256 | The amount of assets to supply. | | `onBehalf` | address | The address that will own the increased collateral position. | | `data` | bytes | Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed. | ### withdrawCollateral [#withdrawcollateral] ```solidity function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external; ``` Withdraws `assets` of collateral on behalf of `onBehalf` to `receiver`. * `msg.sender` must be authorized to manage `onBehalf`'s positions. * Withdrawing an amount corresponding to more collateral than supplied will revert for underflow. #### Parameters: [#parameters-11] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market to withdraw collateral from. | | `assets` | uint256 | The amount of assets to withdraw. | | `onBehalf` | address | The address that owns the collateral position. | | `receiver` | address | The address that will receive the collateral assets. | ### liquidate [#liquidate] ```solidity function liquidate( MarketParams memory marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes memory data ) external returns (uint256, uint256); ``` Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function with the given `data`. * Either `seizedAssets` or `repaidShares` should be zero. * Seizing more than the collateral balance will underflow and revert without any error message. * Repaying more than the borrow balance will underflow and revert without any error message. * An attacker can front-run a liquidation with a small repay making the transaction revert for underflow. #### Parameters: [#parameters-12] | Name | Type | Description | | :------------- | :----------------------------------- | :----------------------------------------------------------------------------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market of the position. | | `borrower` | address | The owner of the position. | | `seizedAssets` | uint256 | The amount of collateral to seize. | | `repaidShares` | uint256 | The amount of shares to repay. | | `data` | bytes | Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed. | #### Return Values: [#return-values-4] | Name | Type | Description | | :------------- | :------ | :--------------------------- | | `assetsSeized` | uint256 | The amount of assets seized. | | `assetsRepaid` | uint256 | The amount of assets repaid. | ### flashLoan [#flashloan] ```solidity function flashLoan(address token, uint256 assets, bytes calldata data) external; ``` Executes a flash loan. * Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all markets combined, plus donations). * Warning: Not ERC-3156 compliant but compatibility is easily reached: a. `flashFee` is zero. b. `maxFlashLoan` is the token's balance of this contract. c. The receiver of `assets` is the caller. Learn more about flash loans on Morpho Blue [here](/learn/concepts/flashloans/). #### Parameters: [#parameters-13] | Name | Type | Description | | :------- | :------ | :---------------------------------------------------------- | | `token` | address | The token to flash loan. | | `assets` | uint256 | The amount of assets to flash loan. | | `data` | bytes | Arbitrary data to pass to the `onMorphoFlashLoan` callback. | ### setAuthorization [#setauthorization] ```solidity function setAuthorization(address authorized, bool newIsAuthorized) external; ``` Sets the authorization for `authorized` to manage `msg.sender`'s positions. #### Parameters: [#parameters-14] | Name | Type | Description | | :---------------- | :------ | :---------------------------- | | `authorized` | address | The authorized address. | | `newIsAuthorized` | bool | The new authorization status. | ### setAuthorizationWithSig [#setauthorizationwithsig] ```solidity struct Authorization { address authorizer; address authorized; bool isAuthorized; uint256 nonce; uint256 deadline; } struct Signature { uint8 v; bytes32 r; bytes32 s; } function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external; ``` Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions. * Warning: Reverts if the signature has already been submitted. * The signature is malleable, but it has no impact on the security here. * The nonce is passed as argument to be able to revert with a different error message. While both functions achieve the same outcome (managing operator permissions), they serve different use cases: * **`setAuthorization`**: Requires an onchain transaction directly from the `authorizer`'s address (or a contract they control). It's simpler for direct interactions but costs gas for the authorizer. * **`setAuthorizationWithSig`**: Uses an EIP-712 signature created offchain by the `authorizer`. This signature can then be submitted onchain by *any* address (e.g., the `authorized` party, a relayer, or the Morpho Bundler). This enables gasless approvals for the authorizer and is essential for meta-transactions and efficient bundling flows, especially with EOAs. #### Authorization struct [#authorization-struct] | Name | Type | Description | | :------------- | :------ | :------------------------------------------------- | | `authorizer` | address | The address authorizing the authorization. | | `authorized` | address | The address to authorize. | | `isAuthorized` | bool | The new authorization status. | | `nonce` | uint256 | The nonce of the authorizer. | | `deadline` | uint256 | The deadline after which the signature is invalid. | #### Parameters: [#parameters-15] | Name | Type | Description | | :-------------- | :------------------------------------- | :------------------------- | | `authorization` | [Authorization](#authorization-struct) | The authorization to set. | | `signature` | Signature | The signature to validate. | ### accrueInterest [#accrueinterest] ```solidity function accrueInterest(MarketParams memory marketParams) external; ``` Accrues interest for the given market `marketParams`. #### Parameters: [#parameters-16] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------- | | `marketParams` | [MarketParams](#marketparams-struct) | The market parameters. | ## Views Functions [#views-functions] ### DOMAIN\_SEPARATOR [#domain_separator] ```solidity function DOMAIN_SEPARATOR() external view returns (bytes32); ``` The EIP-712 domain separator. * Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing the same chain id because the domain separator would be the same. #### Return Values: [#return-values-5] | Type | Description | | :------ | :---------------------------- | | bytes32 | The EIP-712 domain separator. | ### owner [#owner] ```solidity function owner() external view returns (address); ``` The owner of the contract. * It has the power to change the owner. * It has the power to set fees on markets and set the fee recipient. * It has the power to enable but not disable IRMs and LLTVs. #### Return Values: [#return-values-6] | Type | Description | | :------ | :------------------------- | | address | The owner of the contract. | ### feeRecipient [#feerecipient] ```solidity function feeRecipient() external view returns (address); ``` The fee recipient of all markets. * The recipient receives the fees of a given market through a supply position on that market. #### Return Values: [#return-values-7] | Type | Description | | :------ | :------------------------- | | address | The fee recipient address. | ### isIrmEnabled [#isirmenabled] ```solidity function isIrmEnabled(address irm) external view returns (bool); ``` Whether the `irm` is enabled. #### Parameters: [#parameters-17] | Name | Type | Description | | :---- | :------ | :--------------- | | `irm` | address | The irm address. | #### Return Values: [#return-values-8] | Type | Description | | :--- | :------------------------------------------------------------- | | bool | Boolean equals to `true` if irm is enabled, `false` otherwise. | ### isLltvEnabled [#islltvenabled] ```solidity function isLltvEnabled(uint256 lltv) external view returns (bool); ``` Whether the `lltv` is enabled. #### Parameters: [#parameters-18] | Name | Type | Description | | :----- | :------ | :---------------- | | `lltv` | address | The lltv address. | #### Return Values: [#return-values-9] | Type | Description | | :--- | :-------------------------------------------------------------- | | bool | Boolean equals to `true` if lltv is enabled, `false` otherwise. | ### isAuthorized [#isauthorized] ```solidity function isAuthorized(address authorizer, address authorized) external view returns (bool); ``` Whether `authorized` is authorized to modify `authorizer`'s position on all markets. * Anyone is authorized to modify their own positions, regardless of this variable. #### Parameters: [#parameters-19] | Name | Type | Description | | :----------- | :------ | :---------------------- | | `authorizer` | address | The authorizer address. | | `authorized` | address | The authorized address. | #### Return Values: [#return-values-10] | Type | Description | | :--- | :------------------------------------------------------------------------------------------------------------------- | | bool | Boolean equals to `true` if `authorized` address is authorized to modify `authorizer`'s position, `false` otherwise. | ### nonce [#nonce] ```solidity function nonce(address authorizer) external view returns (uint256); ``` The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures. #### Parameters: [#parameters-20] | Name | Type | Description | | :----------- | :------ | :---------------------- | | `authorizer` | address | The authorizer address. | #### Return Values: [#return-values-11] | Type | Description | | :------ | :----------------------- | | uint256 | The current nonce value. | ### extSloads [#extsloads] ```solidity function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory); ``` Returns the data stored on the different `slots`. #### Parameters: [#parameters-21] | Name | Type | Description | | :------ | :--------- | :--------------------------------------------------- | | `slots` | bytes32\[] | The array of slots to retrieve the data stored into. | #### Return Values: [#return-values-12] | Type | Description | | :--------- | :--------------- | | bytes32\[] | The data stored. | ### idToMarketParams [#idtomarketparams] ```solidity function idToMarketParams(Id id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); ``` The market params corresponding to `id`. * This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. #### Parameters: [#parameters-22] | Name | Type | Description | | :--- | :--- | :------------ | | `id` | Id | The market id | #### Return Values: [#return-values-13] | Name | Type | Description | | :---------------- | :------ | :---------------------------------- | | `loanToken` | address | The loan token of the market. | | `collateralToken` | address | The collateral token of the market. | | `oracle` | address | The oracle of the market. | | `irm` | address | The IRM of the market. | | `lltv` | uint256 | The LLTV of the market. | ### market [#market] ```solidity function market(Id id) external view returns ( uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee ); ``` The market state corresponding to `id`. * Interest are not accrued, and assets values are not updated. Their values are the one at the last update. * One can use the [MorphoBalancesLib](#morphobalanceslib) to accrue interests #### Parameters: [#parameters-23] | Name | Type | Description | | :--- | :--- | :------------ | | `id` | Id | The market id | #### Return Values: [#return-values-14] | Name | Type | Description | | :------------------ | :------ | :--------------------------------------- | | `totalSupplyAssets` | uint128 | The total supply assets of the market. | | `totalSupplyShares` | uint128 | The total supply shares of the market. | | `totalBorrowAssets` | uint128 | The total borrow assets of the market. | | `totalBorrowShares` | uint128 | The total borrow shares of the market. | | `lastUpdate` | uint128 | The last update timestamp of the market. | | `fee` | uint128 | The fee of the market. | ### position [#position] ```solidity function position(Id id, address user) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral); ``` The state of the position of `user` on the market corresponding to `id`. #### Parameters: [#parameters-24] | Name | Type | Description | | :----- | :------ | :---------------- | | `id` | Id | The market id. | | `user` | address | The user address. | #### Return Values: [#return-values-15] | Name | Type | Description | | :------------- | :------ | :--------------------------------------------------------- | | `supplyShares` | uint256 | The total supply shares of the user on a given market. | | `borrowShares` | uint128 | The total borrow shares of the user on a given market. | | `collateral` | uint128 | The total collateral assets of the user on a given market. | ## MorphoBalancesLib [#morphobalanceslib] The MorphoBalancesLib is a library that allows you to accrue interest and update the assets values of a market. You can easily retrieve market amounts and user balances with interests. > The Github code is available [**here**](https://github.com/morpho-org/morpho-blue/blob/be2306afd655232d2cf16547c18be0077014bae0/src/libraries/periphery/MorphoBalancesLib.sol) ### expectedMarketBalances [#expectedmarketbalances] ```solidity function expectedMarketBalances(IMorpho morpho, MarketParams memory marketParams) internal view returns ( uint256 totalSupplyAssets, uint256 totalSupplyShares, uint256 totalBorrowAssets, uint256 totalBorrowShares ); ``` Returns the expected market balances of a market after having accrued interests, defined in [Market](#market-struct). #### Parameters: [#parameters-25] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `morpho` | IMorpho | The Morpho contract (injected when used as library). | | `marketParams` | [MarketParams](#marketparams-struct) | The market to get the expected balances from. | #### Return Values: [#return-values-16] | Name | Type | Description | | :------------------ | :------ | :-------------------------------------------------------------- | | `totalSupplyAssets` | uint256 | The total supply assets of the market (with interests accrued). | | `totalSupplyShares` | uint256 | The total supply shares of the market. | | `totalBorrowAssets` | uint256 | The total borrow assets of the market (with interests accrued). | | `totalBorrowShares` | uint256 | The total borrow shares of the market. | ### expectedTotalSupplyAssets [#expectedtotalsupplyassets] ```solidity function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalSupplyAssets); ``` Returns the expected total supply assets of a market after having accrued interests. #### Parameters: [#parameters-26] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `morpho` | IMorpho | The Morpho contract (injected when used as library). | | `marketParams` | [MarketParams](#marketparams-struct) | The market to get the expected total supply from. | #### Return Values: [#return-values-17] | Name | Type | Description | | :------------------ | :------ | :-------------------------------------------------------------- | | `totalSupplyAssets` | uint256 | The total supply assets of the market (with interests accrued). | ### expectedTotalBorrowAssets [#expectedtotalborrowassets] ```solidity function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalBorrowAssets); ``` Returns the expected total borrow assets of a market after having accrued interests. #### Parameters: [#parameters-27] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `morpho` | IMorpho | The Morpho contract (injected when used as library). | | `marketParams` | [MarketParams](#marketparams-struct) | The market to get the expected total borrow from. | #### Return Values: [#return-values-18] | Name | Type | Description | | :------------------ | :------ | :-------------------------------------------------------------- | | `totalBorrowAssets` | uint256 | The total borrow assets of the market (with interests accrued). | ### expectedTotalSupplyShares [#expectedtotalsupplyshares] ```solidity function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalSupplyShares); ``` Returns the expected total supply shares of a market after having accrued interests. * It can grow only if fees > 0 #### Parameters: [#parameters-28] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `morpho` | IMorpho | The Morpho contract (injected when used as library). | | `marketParams` | [MarketParams](#marketparams-struct) | The market to get the expected total supply from. | #### Return Values: [#return-values-19] | Name | Type | Description | | :------------------ | :------ | :-------------------------------------------------------------- | | `totalSupplyShares` | uint256 | The total supply shares of the market (with interests accrued). | ### expectedSupplyAssets [#expectedsupplyassets] ```solidity function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) ``` Returns the expected supply assets of a user on a market after having accrued interests. * Warning: Wrong for `feeRecipient` because their supply shares increase is not taken into account. * Warning: Withdrawing using the expected supply assets can lead to a revert due to conversion roundings from assets to shares. #### Parameters: [#parameters-29] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `morpho` | IMorpho | The Morpho contract (injected when used as library). | | `marketParams` | [MarketParams](#marketparams-struct) | The market to get the expected supply assets from. | | `user` | address | The user to get the expected supply assets from. | #### Return Values: [#return-values-20] | Name | Type | Description | | :------------- | :------ | :------------------------------------------------------ | | `supplyAssets` | uint256 | The supply assets of the user (with interests accrued). | ### expectedBorrowAssets [#expectedborrowassets] ```solidity function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) ``` Returns the expected borrow assets of a user on a market after having accrued interests. * Warning: The expected balance is rounded up, so it may be greater than the market's expected total borrow assets. #### Parameters: [#parameters-30] | Name | Type | Description | | :------------- | :----------------------------------- | :--------------------------------------------------- | | `morpho` | IMorpho | The Morpho contract (injected when used as library). | | `marketParams` | [MarketParams](#marketparams-struct) | The market to get the expected borrow assets from. | | `user` | address | The user to get the expected borrow assets from. | #### Return Values: [#return-values-21] | Name | Type | Description | | :-------------- | :------ | :------------------------------------------------------ | | `borrowBalance` | uint256 | The borrow assets of the user (with interests accrued). | ## Errors Codes [#errors-codes] The full list of custom errors are displayed directly in the ErrorsLib of the [Morpho contracts](https://github.com/morpho-org/morpho-blue/blob/main/src/libraries/ErrorsLib.sol). For example, see the [Morpho contract deployed on Ethereum mainnet](https://etherscan.io/address/0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb#code#F10#L1). Below is the list of the different **Errors** that can be thrown, along with their explanations: | Errors Message (returned data) | Error Natspec | | ------------------------------ | ------------------------------------------------------------------------------- | | "already set" | Thrown when the value is already set. | | "healthy position" | Thrown when the position to liquidate is healthy. | | "inconsistent input" | Thrown when not exactly one of the input amount is zero. | | "insufficient collateral" | Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`. | | "insufficient liquidity" | Thrown when the liquidity is insufficient to `withdraw` or `borrow`. | | "invalid nonce" | Thrown when the nonce is invalid. | | "invalid signature" | Thrown when the authorization signature is invalid. | | "irm not enabled" | Thrown when the IRM is not enabled at market creation. | | "lltv not enabled" | Thrown when the LLTV is not enabled at market creation. | | "market already created" | Thrown when the market is already created. | | "market not created" | Thrown when the market is not created. | | "max fee exceeded" | Thrown when the fee to set exceeds the maximum fee. | | "max lltv exceeded" | Thrown when the LLTV to enable exceeds the maximum LLTV. | | "max uint128 exceeded" | Thrown when the maximum uint128 is exceeded. | | "no code" | Thrown when a token to transfer doesn't have code. | | "not owner" | Thrown when the caller is not the owner. | | "signature expired" | Thrown when the authorization signature is expired. | | "transfer from reverted" | Thrown when a token transferFrom reverted. | | "transfer from returned false" | Thrown when a token transferFrom returned false. | | "transfer returned false" | Thrown when a token transfer returned false. | | "transfer reverted" | Thrown when a token transfer reverted. | | "unauthorized" | Thrown when the caller is not authorized to conduct an action. | | "zero address" | Thrown when a zero address is passed as input. | | "zero assets" | Thrown when zero assets is passed as input. | --- # Bundler3 Source: https://docs.morpho.org/developers/contracts/bundlers ## Bundler3 Structure [#bundler3-structure] Bundler3 is a call dispatcher that enables atomic execution of arbitrary calls with enhanced features for authorization management and callback handling. bundlers3 plot 1. The core [`Bundler3`](https://github.com/morpho-org/bundler3/blob/main/src/Bundler3.sol) contract implements `multicall(Call[] calldata bundle)`, where each call is defined by: * **`to`**: Target address * **`data`**: Calldata * **`value`**: Native currency amount * **`skipRevert`**: Flag to skip reverting if this particular call fails * **`callbackHash`**: Specifies the hash used for controlling reentrancy callbacks 2. **Adapters** all inherit from [`CoreAdapter`](https://github.com/morpho-org/bundler3/blob/main/src/adapters/CoreAdapter.sol), which provides access to the **initiator** (the original caller) via transient storage. This mechanism allows adapters to enforce strict permission checks (e.g. only acting on behalf of the initiator). 3. **Chain-specific adapters**, such as [`EthereumGeneralAdapter1`](https://github.com/morpho-org/bundler3/blob/main/src/adapters/EthereumGeneralAdapter1.sol), extend the base [`GeneralAdapter1`](https://github.com/morpho-org/bundler3/blob/main/src/adapters/GeneralAdapter1.sol) to support network-specific features (e.g. stETH on Ethereum). 4. **Specialized adapters** target specific integrations: * [`ParaswapAdapter`](https://github.com/morpho-org/bundler3/blob/main/src/adapters/ParaswapAdapter.sol) for DEX aggregation (buy/sell/swaps) * **Migration adapters** for moving user positions between protocols (Aave, Compound, Morpho, etc.) # Permit 2 [#permit-2] In November 2022, Uniswap Labs introduced [Permit2](https://blog.uniswap.org/permit2-and-universal-router), a robust token approval contract designed to standardize and secure token approvals across various smart contracts. By enabling signature-based approvals and efficient management of token allowances, Permit2 significantly enhances the user experience. It reduces transaction costs and bolsters smart contract security by mitigating risks associated with traditional approval methods. The integration of Permit2 is set to benefit the entire DeFi ecosystem. Specifically, the Bundlers, operating within the Morpho V0, V1 and V2 frameworks, leverage Permit2's features to streamline transactions. This integration ensures more secure, cost-effective, and user-friendly operations across these platforms. Discover the full potential of Permit2 and how it revolutionizes token approvals in the [integration guide](https://blog.uniswap.org/permit2-integration-guide). --- # Morpho Contracts Source: https://docs.morpho.org/developers/contracts | **Category** | **Contract** | **GitHub repositories** | **Specs** | | --------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | **Midnight** | Midnight | [midnight](https://github.com/morpho-org/midnight) | [spec](/developers/contracts/midnight/) | | **Blue** | Variable Rate Market | [morpho-blue](https://github.com/morpho-org/morpho-blue) | [spec](/developers/contracts/blue/) | | | Interest Rate Model | [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm) | [spec](/developers/contracts/irm/) | | **Morpho Vaults V2** | Vaults V2 | [vault-v2](https://github.com/morpho-org/vault-v2) | [spec](/developers/contracts/morpho-vaults-v2/) | | | MorphoMarketV1AdapterV2 | [vault-v2](https://github.com/morpho-org/vault-v2) | [spec](/developers/contracts/morpho-market-v1-adapter-v2/) | | | MorphoVaultV1Adapter | [vault-v2](https://github.com/morpho-org/vault-v2) | [spec](/developers/contracts/morpho-vault-v1-adapter/) | | **Morpho Vaults V1** | Vaults V1.1 | [metamorpho-v1.1](https://github.com/morpho-org/metamorpho-v1.1) | [spec](/developers/contracts/morpho-vaults/) | | | Vaults V1.0 | [metamorpho](https://github.com/morpho-org/metamorpho) | [spec](/developers/contracts/morpho-vaults/) | | **Protocol Utilities** | Oracle | [morpho-blue-oracles](https://github.com/morpho-org/morpho-blue-oracles) | [spec](/developers/contracts/oracles/) | | | Bundler3 | [bundler3](https://github.com/morpho-org/bundler3) | [spec](/developers/contracts/bundlers/) | | | Universal-Rewards-Distributor | [universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor) | [spec](/developers/contracts/rewards/) | --- # Interest Rate Models Source: https://docs.morpho.org/developers/contracts/irm Interest rate models are defined as a list of governance-approved contracts. Each contract implements the [IRM interface](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IIrm.sol) exposed below. ## Function [#function] ### borrowRate [#borrowrate] ```solidity function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256); ``` Returns the borrow rate of the market `marketParams`. #### Parameters: [#parameters] | Name | Type | Description | | :------------- | :--------------------------------------------------------------------------- | :------------------------------------- | | `marketParams` | [MarketParams](/get-started/resources/contracts/morpho/#marketparams-struct) | The MarketParams struct of the market. | | `market` | [Market](/get-started/resources/contracts/morpho/#market-struct) | The Market struct of the market. | #### Return values: [#return-values] | Name | Type | Description | | :----------- | :------ | :----------------------------- | | `borrowRate` | uint256 | The borrow rate of the market. | ## View Function [#view-function] ### borrowRateView: [#borrowrateview] ```solidity function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); ``` Returns the borrow rate of the market `marketParams` without modifying any storage. #### Parameters: [#parameters-1] | Name | Type | Description | | :------------- | :--------------------------------------------------------------------------- | :------------------------------------- | | `marketParams` | [MarketParams](/get-started/resources/contracts/morpho/#marketparams-struct) | The MarketParams struct of the market. | | `market` | [Market](/get-started/resources/contracts/morpho/#market-struct) | The Market struct of the market. | #### Return values: [#return-values-1] | Name | Type | Description | | :----------- | :------ | :----------------------------- | | `borrowRate` | uint256 | The borrow rate of the market. | Make sure the input parameters correspond to the last time the market was updated (check `lastUpdate`) and apply the returned borrow rate as is applied in Morpho over the period between the market's `lastUpdate` and the query's block timestamp. ## Calculations [#calculations] The Annual Percentage Yields (APY) for both borrowing and supplying are key indicators of the returns for lenders and the cost for borrowers. The APY takes into account the compounding interest to provide a standardized measure of yields over a one-year period. ### Borrow APY [#borrow-apy] The Borrow APY is calculated using the following formula: $$ \text{borrowAPY} = \left(e^{\left(\text{borrowRate} \times \text{secondsPerYear}\right)} - 1\right) $$ Where: * `borrowRate` is the borrow rate per second, as determined by the Interest Rate Model (IRM), * `secondsPerYear` represents the total number of seconds in a year (31,536,000). To obtain the `borrowRate` value, a simple call can be made to the `borrowRateView` or `borrowRate` functions defined in the upper sections. ### Supply APY [#supply-apy] The Supply APY is calculated considering the utilization and the fee. The formula is: The `fee` is the portion of the interest paid by borrowers that is retained by the protocol. For more details, see the [Curator Fee Section](/curate/concepts/fee). $$ \text{supplyAPY} = \text{borrowAPY} \times \text{utilization} \times (1 - \text{fee}) $$ Where: * `fee` is the fee of the market, to be activated by the governance, on a per-market basis, * `utilization` is calculated as: $$ \text{utilization} = \frac{\text{totalBorrowAssets}}{\text{totalSupplyAssets}} $$ # AdaptiveCurveIRM [#adaptivecurveirm] Here is a video that shows how the Adaptive Curve IRM works: ## Overview [#overview] In Morpho, the interest borrowers pay is defined by an external interest rate model (IRM) that can be chosen at market creation among a governance-defined collection. The AdaptiveCurveIRM is the first IRM that will be available for Morpho markets. It fundamentally differs from the ones used in traditional lending pools for two main reasons: * Unlike current lending pools' IRMs which can be updated, the AdaptiveCurveIRM is immutable. It must therefore adapt autonomously to market conditions, such as changes in interest rates on other venues. * In Morpho, the supply is not used as collateral. There is no need to keep markets very liquid to allow liquidations at any time. Removing this systemic risk enables more aggressive target utilization of the capital and lower penalties for illiquidity, enabling more efficient markets. The AdaptiveCurveIRM is designed to adjust utilization to the target utilization, which is set at 90%. There are two time horizons at play: In the short term, we don't want utilization to get too low, or too high and cause liquidity problems. In the medium and long term, we want the rate level to adapt to changing market dynamics. To achieve this, the AdaptiveCurveIRM adjusts user incentives through the action of two different mechanisms: * The Curve Mechanism * The Adaptive Mechanism ## The Curve Mechanism [#the-curve-mechanism] This mechanism is similar to the interest rate curve used in traditional lending pools. The curve is characterized by two parameters: $$ r_{90\%} \text{ which is the target rate at utilization target } u_{target}=0.9 $$ and $$ c=4 $$ a fixed parameter that determines the steepness of the curve above and below the utilization target.
Curve Mechanism 1
At each interaction, utilization will change, resulting in a discontinuous change in the rate determined by the curve. For example: * if utilization is 90%, the rate is $$ r_{90\%} $$ * if utilization is 100%, the rate (APR) is $$ 4*r_{90\%} $$ The aim of the Curve Mechanism is to manage short-term utilization changes. ## The Adaptive Mechanism [#the-adaptive-mechanism] This mechanism continuously shifts the curve to adjust to market conditions over time. Note that the rate follows the shift of the curve. This means that the rate is continuously evolving over time, even when there is no interaction. The shifting of the curve is done by continuously changing the value of r\_90% over time: * When the utilization is above the target utilization, $$ r_{90\%} $$ continuously shifts upwards. * When the utilization is below the target utilization, $$ r_{90\%} $$ continuously shifts downwards. {" "}
Morpho Banner
The speed at which r90% moves is updated at each interaction: the farther we are from the target, the faster r90%, hence the curve, shifts. For example, if the utilization remains at 100% for 5 days, $$ r_{90\%} $$ will progressively double. This is the maximum speed at which $$ r_{90\%} $$ can move. The values of some [constants](https://github.com/morpho-org/morpho-blue-irm/blob/main/src/adaptive-curve-irm/libraries/ConstantsLib.sol) are hardcoded into the code deployed on Ethereum, such as `TARGET_UTILIZATION`, `INITIAL_RATE_AT_TARGET`, etc. ## Formal description [#formal-description] We define: * Utilzation $$ u $$ $$ u(t) $$ is the ratio of total borrow over total supply at time $$ t $$ The utilization target is constant: $$ u\_{target}=0.9 $$ * Error ( $$ e $$ ): $$ \forall t, $$ $$ e(u)=\begin{cases} \frac{u(t)-u_{target}}{1-u_{target}}&\text{if } u(t)>u_{target} \frac{u(t)-u_{target}}{u_{target}}&\text{if } u(t)\le u_{target} \end{cases} $$ It can be seen as the difference between the utilization and the target, divided by a normalization factor. The normalization is here to make the distance between utarget and u = 1 equals to the distance between utarget and $$ u=0 $$
Morpho Banner
* Curve: $$ \text{curve}(u)= \begin{cases} (1-\frac{1}{k_d})*e(u)+1&\text{if } u\le u_{target} (k_d-1)*e(u)+1&\text{if } u>u_{target} \end{cases} $$ with $$ k_d=4 $$ * History of interactions ( $$ \mathcal{H} $$ ): Noting $$ t_i $$ the time at which the $$ i^{th} $$ interaction occurred, $$ \forall t, $$ $$ \mathcal{H}(t)=\{0\}+\{t_i\}_{t_i< t} $$ * Last interaction ( $$ \text{last} $$ $$ \forall t, $$ $$ \text{last}(t) = \max(\mathcal{H}(t)) $$ * Speed factor ( $$ \text{speed} $$ ): $$ \forall t, \text{ speed}(t) = \exp \big({k_P*e(\text{u}(\text{last}(t)))*(t-\text{last}(t))\big)}, \text{ with } k_p = 50 $$ * Rate at target ( $$ r_T $$ $$ r_T(0) $$ is set to an arbitrary value Then, $$ \forall t >0, r_T(t)=r_{T}(\text{last}(t))*\text{speed}(t) $$ At any time t, the borrow rate $$ r $$ is given by the formula: $$ r(t) = r_{T}(t)* \text{curve}(u(t)) $$ ## Implementation [#implementation] > [Adaptive Curve IRM Github repository](https://github.com/morpho-org/morpho-blue-irm) The contract implements the [Interest Rate Model interface](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IIrm.sol) to fit Morpho's specifications. Moreover, the implementation stores configuration variables and a market parameter updated at each `borrowRate` call for this market. ## Variables [#variables] ### Constants [#constants] The values of the following constants are hardcoded into the [code deployed on Ethereum](https://etherscan.io/address/0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC#code#F8#L27) * `CURVE_STEEPNESS`: Curve steepness (scaled by WAD), value = `4`. * `ADJUSTMENT_SPEED`: Adjustment speed per second (scaled by WAD), value = `50/nb of seconds per year`. * `TARGET_UTILIZATION`: Target utilization (scaled by WAD), value = `90%`. * `INITIAL_RATE_AT_TARGET`: Initial rate at target per second (scaled by WAD), value = `4%/nb of seconds per year`. * `MIN_RATE_AT_TARGET`: Minimum rate at target per second (scaled by WAD), value = `0.1%/nb of seconds per year`. * `MAX_RATE_AT_TARGET`: Maximum rate at target per second (scaled by WAD), value = `200%/nb of seconds per year`. ## Immutables [#immutables] * `MORPHO`: Address of Morpho. ## Mappings [#mappings] ### Rate at target [#rate-at-target] ```solidity mapping(Id => uint256) public rateAtTarget; ``` Rate at target utilization for each market. Tells the height of the curve. ## Events [#events] ### BorrowRateUpdate [#borrowrateupdate] ```solidity event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget); ``` Emitted when the borrow rate is updated (called by Morpho). #### Parameters: [#parameters-2] | Name | Type | Description | | :-------------- | :------ | :--------------------------------------- | | `id` | Id | The id of the market. | | `avgBorrowRate` | uint256 | The average borrow rate of the market. | | `rateAtTarget` | uint256 | The stored rate at target of the market. | --- # Midnight Source: https://docs.morpho.org/developers/contracts/midnight ## Market parameters [#market-parameters] ### Market id [#market-id] The market id is a `bytes32` value derived from the market parameters. It is used to identify a market in Midnight and is computed by the `IdLib.toId` function using a `CREATE2`-style hash of the encoded `Market` struct. ```solidity function toId(Market memory market) internal pure returns (bytes32); ``` ### Market struct [#market-struct] ```solidity struct Market { uint256 chainId; address midnight; address loanToken; CollateralParams[] collateralParams; uint256 maturity; uint256 rcfThreshold; address enterGate; address liquidatorGate; } ``` | Name | Type | Description | | :----------------- | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chainId` | uint256 | The chain ID the market is deployed on. | | `midnight` | address | The Midnight contract address. | | `loanToken` | address | The ERC-20 loan token. | | `collateralParams` | CollateralParams\[] | Array of collateral configurations (1 to 128). Entries must be sorted in strictly ascending order by collateral token address, with no duplicate tokens. | | `maturity` | uint256 | Unix timestamp at which the market matures. | | `rcfThreshold` | uint256 | Recovery close factor threshold, expressed in raw loan-token units using the loan token's decimals. Not a percentage. | | `enterGate` | address | Optional gate controlling who can increase credit or debt. Zero address means no restriction. | | `liquidatorGate` | address | Optional gate controlling who can liquidate. Zero address means no restriction. | ### CollateralParams struct [#collateralparams-struct] ```solidity struct CollateralParams { address token; uint256 lltv; uint256 liquidationCursor; address oracle; } ``` | Name | Type | Description | | :------------------ | :------ | :------------------------------------------------------------------------------------- | | `token` | address | The ERC-20 collateral token. | | `lltv` | uint256 | Liquidation loan-to-value ratio (scaled by 1e18). Must be enabled by governance. | | `liquidationCursor` | uint256 | Controls the liquidation incentive (scaled by 1e18). Must be enabled by governance. | | `oracle` | address | Oracle returning the price of 1 collateral unit quoted in loan tokens, scaled by 1e36. | Enabled `lltv` and `liquidationCursor` values must also form a compatible pair. The derived maximum liquidation incentive factor, `maxLif`, is scaled by `1e18`. Market creation requires `maxLif <= 2e18` and, unless `lltv == 1e18`, `lltv * maxLif <= 0.999e36`. ### MarketState struct [#marketstate-struct] ```solidity struct MarketState { uint128 totalUnits; uint128 lossFactor; uint128 withdrawable; uint128 continuousFeeCredit; uint16 settlementFeeCbp0; // ... settlementFeeCbp1 through settlementFeeCbp6 uint32 continuousFee; uint8 tickSpacing; } ``` `totalUnits` tracks total outstanding credit-side units. It is not a debt total. `lossFactor` tracks cumulative slashing. `withdrawable` is the amount of loan tokens lenders can currently withdraw. ### Position struct [#position-struct] ```solidity struct Position { uint128 credit; uint128 pendingFee; uint128 lastLossFactor; uint128 lastAccrual; uint128 debt; uint128 collateralBitmap; uint128[128] collateral; } ``` Each address has one position per market. `credit` and `debt` are in units. `collateralBitmap` tracks which collateral slots are active. `pendingFee` tracks [the continuous fee](/learn/concepts/midnight/#fees) reserved against lender credit. [Settlement fees](/learn/concepts/midnight/#fees) are charged separately when an offer is taken and are not stored in the position. ## Offer struct [#offer-struct] ```solidity struct Offer { Market market; bool buy; address maker; uint256 start; uint256 expiry; uint256 tick; bytes32 group; address callback; bytes callbackData; address receiverIfMakerIsSeller; address ratifier; bool reduceOnly; uint128 maxUnits; uint128 maxAssets; uint256 continuousFeeCap; } ``` | Name | Type | Description | | :------------------------ | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `buy` | bool | `true` if the maker is buying units, `false` if the maker is selling units. Buying can first reduce debt before increasing credit; selling can first reduce credit before increasing debt. | | `maker` | address | Address of the offer's maker. | | `start` | uint256 | Unix timestamp before which the offer cannot be taken. | | `expiry` | uint256 | Unix timestamp after which the offer can no longer be taken. | | `tick` | uint256 | Price tick representing the fixed rate. Must be a multiple of the market's current `tickSpacing`; otherwise `take` reverts. | | `group` | bytes32 | Identifier for the maker’s shared consumption counter. A consistent shared budget assumes the same `buy` direction, loan token, `maxUnits`, and `maxAssets` across offers. The core contract does not enforce these matches. | | `callback` | address | Optional maker callback. For a buy offer, it is called as the buyer callback before loan-token transfers; for a sell offer, it is called as the seller callback after transfers. | | `callbackData` | bytes | Data forwarded to the maker callback. | | `receiverIfMakerIsSeller` | address | Receiver of loan tokens when the maker is the seller (`buy = false`). Must be the zero address when `buy = true`. | | `ratifier` | address | Contract that validates whether the offer can be consumed by a given taker. | | `reduceOnly` | bool | If `true`, the offer may only reduce the maker's position: a maker buy offer may only reduce maker debt, and a maker sell offer may only reduce maker credit. The taker's position is not restricted by this flag. | | `maxUnits` | uint128 | Maximum units the offer can be filled for. | | `maxAssets` | uint128 | Maximum assets (buyer assets if `buy`, seller assets otherwise). | | `continuousFeeCap` | uint256 | Maximum continuous fee rate the maker accepts, expressed per second and scaled by `1e18`. Not an annualized rate. | Exactly one of `maxUnits` and `maxAssets` must be non-zero. Setting both or neither causes `take` to revert. ## Stored state [#stored-state] The following public getters expose raw values stored by the Midnight contract. Position values can be stale because loss socialization and continuous fees are applied lazily; use [`updatePositionView`](#updatepositionview) for an up-to-date calculation. ### position [#position] ```solidity function position(bytes32 id, address user) external view returns ( uint128 credit, uint128 pendingFee, uint128 lastLossFactor, uint128 lastAccrual, uint128 debt, uint128 collateralBitmap ); ``` Returns the raw stored position fields for `user` in market `id`. The fixed-size collateral array is read separately through the collateral getter and is not returned here. ### marketState [#marketstate] ```solidity function marketState(bytes32 id) external view returns ( uint128 totalUnits, uint128 lossFactor, uint128 withdrawable, uint128 continuousFeeCredit, uint16 settlementFeeCbp0, uint16 settlementFeeCbp1, uint16 settlementFeeCbp2, uint16 settlementFeeCbp3, uint16 settlementFeeCbp4, uint16 settlementFeeCbp5, uint16 settlementFeeCbp6, uint32 continuousFee, uint8 tickSpacing ); ``` Returns the raw accounting and configuration state for market `id`, including total units, loss factor, withdrawable liquidity, accrued continuous-fee credit, settlement-fee breakpoints, continuous-fee rate, and tick spacing. ### consumed [#consumed] ```solidity function consumed(address user, bytes32 group) external view returns (uint128); ``` Returns how much of `user`'s shared offer-group capacity has already been consumed. Depending on the offers in the group, the value represents units or assets. ### isAuthorized [#isauthorized] ```solidity function isAuthorized(address authorizer, address authorized) external view returns (bool); ``` Returns the stored authorization flag indicating whether `authorized` may act on behalf of `authorizer`. ### isLltvEnabled [#islltvenabled] ```solidity function isLltvEnabled(uint256 lltv) external view returns (bool); ``` Returns whether the WAD-scaled `lltv` value is enabled for market creation. ### isLiquidationCursorEnabled [#isliquidationcursorenabled] ```solidity function isLiquidationCursorEnabled(uint256 liquidationCursor) external view returns (bool); ``` Returns whether the WAD-scaled `liquidationCursor` value is enabled for market creation. ## Selected functions [#selected-functions] ### take [#take] ```solidity function take( Offer memory offer, bytes memory ratifierData, uint256 units, address taker, address receiverIfTakerIsSeller, address takerCallback, bytes memory takerCallbackData ) external returns (uint256 buyerAssets, uint256 sellerAssets); ``` Executes a trade against an offer. Buying units first nets existing debt and only then increases credit; selling units first nets existing credit and only then increases debt. Settlement transfers loan tokens between the parties according to the matched tick price. `take` calculates settlement fees at execution. If the fee changes after a quote, the taker may pay more or receive less than quoted. The function has no maximum-payment or minimum-receipt parameter. #### Parameters: [#parameters] | Name | Type | Description | | :------------------------ | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `offer` | Offer | The offer to consume. | | `ratifierData` | bytes | Data passed to the offer's ratifier for validation. | | `units` | uint256 | Number of units to fill (must not exceed the offer's remaining capacity). | | `taker` | address | Address taking the opposite side of the offer. The caller must be `taker` or an account authorized by `taker` on Midnight. | | `receiverIfTakerIsSeller` | address | Receiver of loan tokens when the taker is the seller (`offer.buy = true`). Must be the zero address when `offer.buy = false`. | | `takerCallback` | address | Optional taker callback. When `offer.buy = false`, it is the buyer callback and runs before loan-token transfers. When `offer.buy = true`, it is the seller callback and runs after transfers. | | `takerCallbackData` | bytes | Data forwarded to the taker callback. | #### Return values: [#return-values] | Name | Type | Description | | :------------- | :------ | :-------------------------------------- | | `buyerAssets` | uint256 | Loan tokens transferred from the buyer. | | `sellerAssets` | uint256 | Loan tokens received by the seller. | ### withdraw [#withdraw] ```solidity function withdraw(Market memory market, uint256 units, address onBehalf, address receiver) external; ``` Redeems credit units for loan tokens, subject to the position's available credit and the market's `withdrawable` liquidity. #### Parameters: [#parameters-1] | Name | Type | Description | | :--------- | :------ | :----------------------------------------------------------------------------------------------------------------- | | `market` | Market | The market to withdraw from. | | `units` | uint256 | Credit units to redeem. | | `onBehalf` | address | Address whose credit is reduced. The caller must be `onBehalf` or an account authorized by `onBehalf` on Midnight. | | `receiver` | address | Address that receives the loan tokens. | ### repay [#repay] ```solidity function repay(Market memory market, uint256 units, address onBehalf, address callback, bytes memory data) external; ``` Repays debt units, releasing the borrower's obligation. Accepts an optional callback for flash-repay patterns. #### Parameters: [#parameters-2] | Name | Type | Description | | :--------- | :------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `market` | Market | The market to repay in. | | `units` | uint256 | Debt units to repay. | | `onBehalf` | address | Borrower whose debt is reduced. The caller must be `onBehalf` or an account authorized by `onBehalf` on Midnight. | | `callback` | address | Optional repayment callback. If nonzero, it must hold `units` of the loan token and approve Midnight before returning `CALLBACK_SUCCESS`. Midnight then pulls repayment from the callback with `transferFrom`. If zero, repayment is pulled from the caller. | | `data` | bytes | Data forwarded to the callback. | ### supplyCollateral [#supplycollateral] ```solidity function supplyCollateral(Market memory market, uint256 collateralIndex, uint256 assets, address onBehalf) external; ``` Deposits collateral for a borrower into a given collateral slot. #### Parameters: [#parameters-3] | Name | Type | Description | | :---------------- | :------ | :----------------------------------------------------------------------------------------------------------------------------- | | `market` | Market | The market to supply collateral to. | | `collateralIndex` | uint256 | Index into `market.collateralParams`. | | `assets` | uint256 | Amount of collateral to deposit. | | `onBehalf` | address | Borrower who receives the collateral credit. The caller must be `onBehalf` or an account authorized by `onBehalf` on Midnight. | ### withdrawCollateral [#withdrawcollateral] ```solidity function withdrawCollateral( Market memory market, uint256 collateralIndex, uint256 assets, address onBehalf, address receiver ) external; ``` Withdraws collateral for a borrower, provided the position remains healthy after withdrawal. #### Parameters: [#parameters-4] | Name | Type | Description | | :---------------- | :------ | :------------------------------------------------------------------------------------------------------------------------ | | `market` | Market | The market to withdraw collateral from. | | `collateralIndex` | uint256 | Index into `market.collateralParams`. | | `assets` | uint256 | Amount of collateral to withdraw. | | `onBehalf` | address | Borrower whose collateral is withdrawn. The caller must be `onBehalf` or an account authorized by `onBehalf` on Midnight. | | `receiver` | address | Address that receives the collateral. | ### liquidate [#liquidate] ```solidity function liquidate( Market memory market, uint256 collateralIndex, uint256 seizedAssets, uint256 repaidUnits, address borrower, bool postMaturityMode, address receiver, address callback, bytes memory data ) external returns (uint256 outputSeizedAssets, uint256 outputRepaidUnits); ``` Liquidates a borrower in normal mode when the position is unhealthy, or in post-maturity mode after maturity regardless of health. Set `postMaturityMode` to `false` for normal mode and `true` for post-maturity mode. After maturity, an unhealthy borrower can be eligible for either mode. At least one of the `seizedAssets` or `repaidUnits` inputs must be `0`. When one is provided, the contract computes the other and returns both final values. Passing both inputs as `0` is permitted to realize bad debt without transferring tokens. With a nonzero `callback`, Midnight sends seized collateral to `receiver` and calls `onLiquidate`, which must return `CALLBACK_SUCCESS`. Midnight then pulls the final `repaidUnits` of the loan token from the callback using `transferFrom`. For a nonzero repayment, the callback must hold those tokens and approve Midnight before returning. With no callback, repayment is pulled from the caller. #### Return values: [#return-values-1] | Name | Type | Description | | :------------------- | :------ | :------------------------------------------ | | `outputSeizedAssets` | uint256 | Collateral assets seized by the liquidator. | | `outputRepaidUnits` | uint256 | Debt units repaid. | ### flashLoan [#flashloan] ```solidity function flashLoan(address[] memory tokens, uint256[] memory assets, address callback, bytes memory data) external; ``` Flash-loans any tokens held by the contract. Midnight transfers the assets to `callback`, calls `onFlashLoan`, then pulls the same amounts back with `transferFrom`. Before returning `CALLBACK_SUCCESS`, the callback must hold the repayment amounts and approve Midnight to spend them. ### touchMarket [#touchmarket] ```solidity function touchMarket(Market memory market) external returns (bytes32); ``` Creates a market if it does not yet exist and returns its id. Safe to call on an already-created market. ### updatePosition [#updateposition] ```solidity function updatePosition(Market memory market, address user) external returns (uint128 newCredit, uint128 newPendingFee, uint128 accruedFee); ``` Applies accumulated slashing and continuous-fee accrual to a position. `withdraw` calls the internal update automatically, while `take` calls it when needed for existing or newly created credit. `repay`, `supplyCollateral`, `withdrawCollateral`, and `liquidate` do not call it directly. ## Utility functions [#utility-functions] ### multicall [#multicall] ```solidity function multicall(bytes[] memory calls) external; ``` Executes encoded Midnight calls sequentially in one transaction. Each call uses `delegatecall`, and the entire batch reverts with the failing call's error if any call fails. ### setConsumed [#setconsumed] ```solidity function setConsumed(bytes32 group, uint128 amount, address onBehalf) external; ``` Sets the consumed amount for `onBehalf`'s offer group. `amount` must be equal to or greater than the current value, so the counter can never decrease. Setting it to `type(uint128).max` cancels all remaining offers in the group. ### setIsAuthorized [#setisauthorized] ```solidity function setIsAuthorized(address authorized, bool newIsAuthorized, address onBehalf) external; ``` Grants or revokes persistent authorization for `authorized` to act on `onBehalf`'s Midnight state. Authorization applies across all markets in this Midnight contract. An authorized account can also grant or revoke authorization for other accounts on behalf of `onBehalf`. ## Computed views [#computed-views] For `updatePositionView` and `isHealthy`, callers must ensure that `id` corresponds to `market` (`id == IdLib.toId(market)`). These functions do not validate the relationship. ### updatePositionView [#updatepositionview] ```solidity function updatePositionView(Market memory market, bytes32 id, address user) external view returns (uint128 newCredit, uint128 newPendingFee, uint128 accruedFee); ``` Calculates the position after applying pending loss-factor slashing and continuous-fee accrual, without modifying storage. Returns the resulting credit, pending fee, and accrued fee. ### toMarket [#tomarket] ```solidity function toMarket(bytes32 id) external view returns (Market memory); ``` Reconstructs and returns the immutable `Market` parameters stored for `id`. Reverts if the market has not been created. ### isHealthy [#ishealthy] ```solidity function isHealthy(Market memory market, bytes32 id, address borrower) external view returns (bool); ``` Returns whether the borrower's collateral supports their debt. For positions with debt, it prices each active collateral, applies its LLTV, sums the resulting maximum debt, and checks that `maxDebt >= debt`. ### settlementFee [#settlementfee] ```solidity function settlementFee(bytes32 id, uint256 timeToMaturity) external view returns (uint256); ``` Returns the WAD-scaled settlement-fee rate for the supplied time to maturity. The value is linearly interpolated between the market's configured fee breakpoints and uses the longest breakpoint at and beyond 360 days. ## Interfaces [#interfaces] ### IGate [#igate] Gates are optional access-control contracts set at market creation. Two gate roles are available: ```solidity interface IEnterGate { function canIncreaseCredit(address account) external view returns (bool); function canIncreaseDebt(address account) external view returns (bool); } interface ILiquidatorGate { function canLiquidate(address account) external view returns (bool); } ``` Gates cannot lock or seize user assets. They only control whether an account may enter a position or liquidate. ### IRatifier [#iratifier] Ratifiers validate offers at execution. Every offer specifies a ratifier; the maker must authorize it, and `take` succeeds only if `isRatified(...)` returns `CALLBACK_SUCCESS`. ```solidity interface IRatifier { function isRatified(Offer memory offer, bytes memory ratifierData, address taker) external view returns (bytes32); } ``` Must return `keccak256("morpho.midnight.callbackSuccess")` to approve the take. ### Callbacks [#callbacks] Five callback interfaces are available for flash patterns: ```solidity interface IBuyCallback { function onBuy(bytes32 id, Market memory market, uint256 buyerAssets, uint256 units, uint256 pendingFeeIncrease, address buyer, bytes memory data) external returns (bytes32); } interface ISellCallback { function onSell(bytes32 id, Market memory market, uint256 sellerAssets, uint256 units, uint256 pendingFeeDecrease, address seller, address receiver, bytes memory data) external returns (bytes32); } interface IRepayCallback { function onRepay(bytes32 id, Market memory market, uint256 units, address onBehalf, bytes memory data) external returns (bytes32); } interface ILiquidateCallback { function onLiquidate(address caller, bytes32 id, Market memory market, uint256 collateralIndex, uint256 seizedAssets, uint256 repaidUnits, address borrower, address receiver, bytes memory data, uint256 badDebt) external returns (bytes32); } interface IFlashLoanCallback { function onFlashLoan(address caller, address[] memory tokens, uint256[] memory assets, bytes memory data) external returns (bytes32); } ``` All callbacks must return `keccak256("morpho.midnight.callbackSuccess")`. ## Constants [#constants] | Constant | Value | Description | | :----------------------------- | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | | `WAD` | `1e18` | Fixed-point scale. | | `ORACLE_PRICE_SCALE` | `1e36` | Oracle price scale. | | `CBP` | `1e12` | Cent basis point scale used for fees. | | `MAX_CONTINUOUS_FEE` | `0.01e18 / 365 days` | Maximum continuous fee rate per second. | | `TIME_TO_MAX_LIF` | `60 minutes` | Time from maturity for the liquidation incentive to reach its maximum in post-maturity mode. Normal mode uses the maximum immediately. | | `MAX_COLLATERALS` | `128` | Maximum collateral types per market. | | `MAX_COLLATERALS_PER_BORROWER` | `16` | Maximum active collateral slots per borrower position. | | `DEFAULT_TICK_SPACING` | `4` | Default tick spacing for new markets. | ## Periphery and ratifiers [#periphery-and-ratifiers] Optional helper contracts are available in the [periphery folder](https://github.com/morpho-org/midnight/tree/main/src/periphery): * **BlueBuyCallback** - callback that funds buy offers by withdrawing loan tokens previously supplied to a Morpho Blue market on behalf of the callback contract. * **EcrecoverAuthorizer** - enables signature-based authorization changes after the helper has been authorized on Midnight for the relevant account. Signed changes are submitted through the helper and executed onchain. Ratifier contracts are available in the [ratifiers folder](https://github.com/morpho-org/midnight/tree/main/src/ratifiers): * **SetterRatifier** - verifies that an offer belongs to a Merkle root ratified by its maker. --- # MorphoMarketV1AdapterV2 Source: https://docs.morpho.org/developers/contracts/morpho-market-v1-adapter-v2 ## Introduction [#introduction] `MorphoMarketV1AdapterV2` is an adapter that connects a Morpho Vault V2 to a variable rate market. It manages supply positions across multiple markets and implements its own timelock system using the same submit/execute pattern as Morpho Vault V2, with its own distinct set of timelocked functions. This adapter must be used with variable rate markets that are protected against inflation attacks with an initial supply. See [OpenZeppelin ERC4626 inflation attack](https://docs.openzeppelin.com/contracts/5.x/erc4626#inflation-attack) and Inflation Front-Running Attack Protection section [here](/curate/concepts/security-considerations/#inflation-front-running-attack-protection). This adapter can only be used for markets with the AdaptiveCurveIRM. Markets are removed from `marketIds` when the allocation is zero, but this does not mean that the adapter has zero shares on that market. *** ### Function Selectors [#function-selectors] #### Timelocked Functions and Selectors [#timelocked-functions-and-selectors] | Function Name | Selector | | :----------------- | :----------- | | `increaseTimelock` | `0x47966291` | | `decreaseTimelock` | `0x5c1a1a4f` | | `abdicate` | `0xb2e32848` | | `setSkimRecipient` | `0x2b30997b` | | `burnShares` | `0x3b2de5ac` | ## Immutables [#immutables] | Name | Type | Description | | :----------------- | :------ | :------------------------------------------------------------------------------------------------------- | | `factory` | address | The factory contract that deployed this adapter. | | `parentVault` | address | The Morpho Vault V2 that this adapter belongs to. | | `asset` | address | The underlying asset of the parent vault (must match the loan token of all managed markets). | | `morpho` | address | The Morpho (Morpho Blue) contract address. | | `adapterId` | bytes32 | Unique identifier for this adapter instance, computed as `keccak256(abi.encode("this", address(this)))`. | | `adaptiveCurveIrm` | address | The AdaptiveCurveIRM address. All managed markets must use this IRM. | ## Timelock System [#timelock-system] The timelock system works identically to the one in Morpho Vault V2. The curator must `submit` a timelocked action, wait for the timelock to expire, then anyone can call the function directly. *** ### `submit` [#submit] ```solidity function submit(bytes calldata data) external ``` Submits a timelocked action for later execution. **Callable by:** `Curator` #### Parameters: [#parameters] | Name | Type | Description | | :----- | :---- | :---------------------------------- | | `data` | bytes | The ABI-encoded function call data. | *** ### `revoke` [#revoke] ```solidity function revoke(bytes calldata data) external ``` Cancels a pending timelocked action. **Callable by:** `Curator`, `Sentinel` #### Parameters: [#parameters-1] | Name | Type | Description | | :----- | :---- | :-------------------------------------------- | | `data` | bytes | The ABI-encoded function call data to revoke. | *** ### `increaseTimelock` [#increasetimelock] ```solidity function increaseTimelock(bytes4 selector, uint256 newDuration) external ``` Increases the timelock duration for a specific function selector. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x47966291` #### Parameters: [#parameters-2] | Name | Type | Description | | :------------ | :------ | :------------------------------------------------------------------------------------------- | | `selector` | bytes4 | The function selector whose timelock to increase. | | `newDuration` | uint256 | The new timelock duration in seconds. Must be greater than or equal to the current duration. | *** ### `decreaseTimelock` [#decreasetimelock] ```solidity function decreaseTimelock(bytes4 selector, uint256 newDuration) external ``` Decreases the timelock duration for a specific function selector. This action's own timelock equals the current timelock of the function being decreased. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x5c1a1a4f` #### Parameters: [#parameters-3] | Name | Type | Description | | :------------ | :------ | :---------------------------------------------------------------------------------------- | | `selector` | bytes4 | The function selector whose timelock to decrease. | | `newDuration` | uint256 | The new timelock duration in seconds. Must be less than or equal to the current duration. | *** ### `abdicate` [#abdicate] ```solidity function abdicate(bytes4 selector) external ``` Irreversibly disables the ability to execute a specific type of action. This action is irreversible. Existing pending operations submitted before abdicating cannot be executed. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0xb2e32848` #### Parameters: [#parameters-4] | Name | Type | Description | | :--------- | :----- | :--------------------------------------------- | | `selector` | bytes4 | The function selector to permanently abdicate. | ## Curator Functions [#curator-functions] ### `setSkimRecipient` [#setskimrecipient] ```solidity function setSkimRecipient(address newSkimRecipient) external ``` Sets the address that receives skimmed token balances (e.g., reward tokens earned by the adapter). **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x2b30997b` #### Parameters: [#parameters-5] | Name | Type | Description | | :----------------- | :------ | :------------------------------ | | `newSkimRecipient` | address | The new skim recipient address. | *** ### `burnShares` [#burnshares] ```solidity function burnShares(bytes32 marketId) external ``` Burns the adapter's tracked supply shares for a market, setting `supplyShares[marketId]` to zero. Burnt shares are lost forever and cannot be recovered. When submitting `burnShares`, it is recommended to set the caps of the market to zero first to avoid losing more assets. After burning shares, call `deallocate` on the parent vault with this adapter's address, the market's data encoding, and `0` assets to sync the vault's recorded allocation. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x3b2de5ac` #### Parameters: [#parameters-6] | Name | Type | Description | | :--------- | :------ | :---------------------------------- | | `marketId` | bytes32 | The market ID whose shares to burn. | ## Other Functions [#other-functions] ### `skim` [#skim] ```solidity function skim(address token) external ``` Transfers the adapter's entire balance of `token` to `skimRecipient`. **Callable by:** `skimRecipient` If `skimRecipient` is not set (`address(0)`), this function will always revert. #### Parameters: [#parameters-7] | Name | Type | Description | | :------ | :------ | :----------------- | | `token` | address | The token to skim. | *** ### `allocate` [#allocate] ```solidity function allocate(bytes memory data, uint256 assets, bytes4, address) external returns (bytes32[] memory, int256) ``` Supplies `assets` into the specified variable rate market. **Callable by:** `parentVault` #### Parameters: [#parameters-8] | Name | Type | Description | | :------- | :------ | :--------------------------------------------------------------- | | `data` | bytes | ABI-encoded `MarketParams` struct identifying the target market. | | `assets` | uint256 | The amount of assets to supply. | The `bytes4` and `address` parameters are part of the shared adapter interface but unused by this implementation. #### Return Values: [#return-values] | Name | Type | Description | | :------ | :--------- | :------------------------------------------------------------------------------------------------------------------ | | `ids` | bytes32\[] | The allocation IDs affected by this operation. | | `delta` | int256 | The change in allocation (new allocation minus old allocation). Positive for `allocate`, negative for `deallocate`. | *** ### `deallocate` [#deallocate] ```solidity function deallocate(bytes memory data, uint256 assets, bytes4, address) external returns (bytes32[] memory, int256) ``` Withdraws `assets` from the specified variable rate market back to the parent vault. **Callable by:** `parentVault` #### Parameters: [#parameters-9] | Name | Type | Description | | :------- | :------ | :--------------------------------------------------------------- | | `data` | bytes | ABI-encoded `MarketParams` struct identifying the target market. | | `assets` | uint256 | The amount of assets to withdraw. | The `bytes4` and `address` parameters are part of the shared adapter interface but unused by this implementation. #### Return Values: [#return-values-1] | Name | Type | Description | | :------ | :--------- | :------------------------------------------------------------------------------------------------------------------ | | `ids` | bytes32\[] | The allocation IDs affected by this operation. | | `delta` | int256 | The change in allocation (new allocation minus old allocation). Positive for `allocate`, negative for `deallocate`. | ## View Functions [#view-functions] ### `realAssets` [#realassets] ```solidity function realAssets() external view returns (uint256) ``` Returns the total expected supply assets across all managed markets, used by the parent vault during interest accrual. #### Return Values: [#return-values-2] | Name | Type | Description | | :------- | :------ | :-------------------------------------------------- | | `assets` | uint256 | The total assets across all markets in `marketIds`. | *** ### `expectedSupplyAssets` [#expectedsupplyassets] ```solidity function expectedSupplyAssets(bytes32 marketId) public view returns (uint256) ``` Returns the expected supply assets for a specific market, accounting for the adapter's internal share balance. #### Parameters: [#parameters-10] | Name | Type | Description | | :--------- | :------ | :---------------------- | | `marketId` | bytes32 | The market ID to query. | #### Return Values: [#return-values-3] | Name | Type | Description | | :------- | :------ | :----------------------------------------------- | | `assets` | uint256 | The expected supply assets for the given market. | *** ### `allocation` [#allocation] ```solidity function allocation(MarketParams memory marketParams) public view returns (uint256) ``` Returns the vault's current allocation for this market, as recorded in the parent vault. #### Parameters: [#parameters-11] | Name | Type | Description | | :------------- | :----------- | :---------------------------- | | `marketParams` | MarketParams | The market parameters struct. | #### Return Values: [#return-values-4] | Name | Type | Description | | :------- | :------ | :------------------------------------------------- | | `assets` | uint256 | The current allocation amount in the parent vault. | *** ### `ids` [#ids] ```solidity function ids(MarketParams memory marketParams) public view returns (bytes32[] memory) ``` Returns the three allocation IDs used by this adapter for a given market. #### Parameters: [#parameters-12] | Name | Type | Description | | :------------- | :----------- | :---------------------------- | | `marketParams` | MarketParams | The market parameters struct. | #### Return Values: [#return-values-5] | Name | Type | Description | | :----- | :--------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ids_` | bytes32\[] | Array of 3 IDs: `adapterId`, `keccak256(abi.encode("collateralToken", collateralToken))`, and `keccak256(abi.encode("this/marketParams", address(this), marketParams))`. | *** ### `marketIdsLength` [#marketidslength] ```solidity function marketIdsLength() external view returns (uint256) ``` Returns the number of markets currently tracked by this adapter. Use this as the upper bound when iterating over [`marketIds`](#marketids). #### Return Values: [#return-values-6] | Name | Type | Description | | :------- | :------ | :--------------------------------------- | | `length` | uint256 | The number of market IDs in `marketIds`. | *** ### `marketIds` [#marketids] ```solidity bytes32[] public marketIds ``` Returns the market ID at the given position in the list of markets with non-zero allocation currently managed by this adapter. #### Parameters: [#parameters-13] | Name | Type | Description | | :------ | :------ | :------------------------------------------------------------------------------------ | | `index` | uint256 | The position in the array. Must be less than [`marketIdsLength()`](#marketidslength). | #### Return Values: [#return-values-7] | Name | Type | Description | | :--------- | :------ | :-------------------------------- | | `marketId` | bytes32 | The market ID at the given index. | *** ### `timelock` [#timelock] ```solidity mapping(bytes4 selector => uint256) public timelock ``` Returns the timelock duration (in seconds) for a given function selector. #### Parameters: [#parameters-14] | Name | Type | Description | | :--------- | :----- | :------------------------------ | | `selector` | bytes4 | The function selector to query. | #### Return Values: [#return-values-8] | Name | Type | Description | | :--------- | :------ | :------------------------------------------------------- | | `duration` | uint256 | The timelock duration in seconds for the given selector. | *** ### `abdicated` [#abdicated] ```solidity mapping(bytes4 selector => bool) public abdicated ``` Returns whether the curator has permanently abdicated the ability to execute a specific action. #### Parameters: [#parameters-15] | Name | Type | Description | | :--------- | :----- | :------------------------------ | | `selector` | bytes4 | The function selector to query. | #### Return Values: [#return-values-9] | Name | Type | Description | | :------------ | :--- | :------------------------------------------------------------------------- | | `isAbdicated` | bool | `true` if the curator has abdicated the given selector, `false` otherwise. | *** ### `executableAt` [#executableat] ```solidity function executableAt(bytes memory data) external view returns (uint256) ``` Returns the Unix timestamp (seconds) at which a submitted timelocked action becomes executable. #### Parameters: [#parameters-16] | Name | Type | Description | | :----- | :---- | :-------------------------------------------------------- | | `data` | bytes | The ABI-encoded function call data of the pending action. | #### Return Values: [#return-values-10] | Value | Meaning | | :------------------- | :--------------------------------------------------------------------- | | `0` | No pending action (never submitted, already executed, or revoked) | | `<= block.timestamp` | Action is currently executable (timelock has elapsed) | | `> block.timestamp` | Not yet executable - equals `submissionTimestamp + timelock[selector]` | *** ### `supplyShares` [#supplyshares] ```solidity mapping(bytes32 marketId => uint256) public supplyShares ``` Returns the adapter's tracked supply shares for a given market. #### Parameters: [#parameters-17] | Name | Type | Description | | :--------- | :------ | :---------------------- | | `marketId` | bytes32 | The market ID to query. | #### Return Values: [#return-values-11] | Name | Type | Description | | :------- | :------ | :-------------------------------------------------------- | | `shares` | uint256 | The adapter's tracked supply shares for the given market. | *** ### `skimRecipient` [#skimrecipient] ```solidity address public skimRecipient ``` Returns the address that receives skimmed token balances. *** --- # Morpho Optimizers (Deprecated) Source: https://docs.morpho.org/developers/contracts/morpho-v0 Morpho V0 has been **deprecated** following the vote of the [MIP 120](https://snapshot.box/#/s\:morpho.eth/proposal/0x026c3360d4616532952f5ed0d728daa090dd9a9670c7bf58160cd0709c7d73f8) and [MIP 121](https://snapshot.box/#/s\:morpho.eth/proposal/0x60b31cceca2beee55f8845f86b7988ee36f29ad50f5dda636164ab50b561936b). If you have a position on a Morpho V0 protocol, please reach out via the chatbox on [the Morpho Help Page](https://help.morpho.org/). ## Morpho V0 deployment addresses [#morpho-v0-deployment-addresses] | Contract | Address | Source Code | | :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | | Morpho V0 (AaveV3 Optimizer - ETH emode) | [ 0x33333aea097c193e66081E930c33020272b33333 ](https://etherscan.io/address/0x33333aea097c193e66081E930c33020272b33333) | [morpho-org/morpho-aave-v3](https://github.com/morpho-org/morpho-aave-v3/tree/main) | | Morpho V0 (AaveV2 Optimizer) | [ 0x777777c9898D384F785Ee44Acfe945efDFf5f3E0 ](https://etherscan.io/address/0x777777c9898D384F785Ee44Acfe945efDFf5f3E0) | [morpho-org/morpho-optimizers](https://github.com/morpho-org/morpho-optimizers) | | Morpho V0 (AaveV2 Lens) | [ 0x507fa343d0a90786d86c7cd885f5c49263a91ff4 ](https://etherscan.io/address/0x507fA343d0A90786d86C7cd885f5C49263A91FF4) | [morpho-org/morpho-optimizers](https://github.com/morpho-org/morpho-optimizers) | | Morpho V0 (CompoundV2 Optimizer) | [ 0x8888882f8f843896699869179fB6E4f7e3B58888 ](https://etherscan.io/address/0x8888882f8f843896699869179fB6E4f7e3B58888) | [morpho-org/morpho-optimizers](https://github.com/morpho-org/morpho-optimizers) | | Morpho V0 (CompoundV2 Lens) | [ 0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67 ](https://etherscan.io/address/0x930f1b46e1D081Ec1524efD95752bE3eCe51EF67) | [morpho-org/morpho-optimizers](https://github.com/morpho-org/morpho-optimizers) | | Bulker V0 (Gateway) | [ 0xF99e3796f94000462f736925Cf32162Fea82c168 ](https://etherscan.io/address/0xF99e3796f94000462f736925Cf32162Fea82c168) | [morpho-org/morpho-aave-v3](https://github.com/morpho-org/morpho-aave-v3/blob/main/src/extensions/BulkerGateway.sol) | ### Tokenized Supply Vaults [#tokenized-supply-vaults] ABI are different for Morpho V0 (AaveV2) & Morpho V0 (CompoundV2) vaults, and all the vaults are deployed on the Ethereum mainnet with 18 decimals. They are fully compliant with the [ERC4626](https://eips.ethereum.org/EIPS/eip-4626) standard.
| Contract | Address | Source Code | | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------ | | maWBTC | [ 0xd508F85F1511aAeC63434E26aeB6d10bE0188dC7 ](https://etherscan.io/address/0xd508F85F1511aAeC63434E26aeB6d10bE0188dC7) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | maWETH | [ 0x490BBbc2485e99989Ba39b34802faFa58e26ABa4 ](https://etherscan.io/address/0x490BBbc2485e99989Ba39b34802faFa58e26ABa4) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | maDAI | [ 0x36F8d0D0573ae92326827C4a82Fe4CE4C244cAb6 ](https://etherscan.io/address/0x36F8d0D0573ae92326827C4a82Fe4CE4C244cAb6) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | maUSDC | [ 0xA5269A8e31B93Ff27B887B56720A25F844db0529 ](https://etherscan.io/address/0xA5269A8e31B93Ff27B887B56720A25F844db0529) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | maUSDT | [ 0xAFe7131a57E44f832cb2dE78ade38CaD644aaC2f ](https://etherscan.io/address/0xAFe7131a57E44f832cb2dE78ade38CaD644aaC2f) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | maCRV | [ 0x9dc7094530cB1bcf5442c3b9389ee386738A190c ](https://etherscan.io/address/0x9dc7094530cB1bcf5442c3b9389ee386738A190c) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcDAI | [ 0x8F88EaE3e1c01d60bccdc3DB3CBD5362Dd55d707 ](https://etherscan.io/address/0x8F88EaE3e1c01d60bccdc3DB3CBD5362Dd55d707) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcWETH | [ 0x676E1B7d5856f4f69e10399685e17c2299370E95 ](https://etherscan.io/address/0x676E1B7d5856f4f69e10399685e17c2299370E95) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcCOMP | [ 0xaA768b85eC827cCc36D882c1814bcd27ec4A8593 ](https://etherscan.io/address/0xaA768b85eC827cCc36D882c1814bcd27ec4A8593) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcUNI | [ 0x496da625C736a2fF122638Dc26dCf1bFdEf1778c ](https://etherscan.io/address/0x496da625C736a2fF122638Dc26dCf1bFdEf1778c) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcUSDC | [ 0xba9E3b3b684719F80657af1A19DEbc3C772494a0 ](https://etherscan.io/address/0xba9E3b3b684719F80657af1A19DEbc3C772494a0) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcUSDT | [ 0xC2A4fBA93d4120d304c94E4fd986e0f9D213eD8A ](https://etherscan.io/address/0xC2A4fBA93d4120d304c94E4fd986e0f9D213eD8A) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | mcWBTC | [ 0xF31AC95fe692190b9C67112d8c912bA9973944F2 ](https://etherscan.io/address/0xF31AC95fe692190b9C67112d8c912bA9973944F2) | [morpho-optimizers-vaults](https://github.com/morpho-org/morpho-optimizers-vaults) | | ma3WETH | [ 0x39Dd7790e75C6F663731f7E1FdC0f35007D3879b ](https://etherscan.io/address/0x39Dd7790e75C6F663731f7E1FdC0f35007D3879b) | [morpho-aave-v3](https://github.com/morpho-org/morpho-aave-v3/blob/main/src/extensions/SupplyVault.sol) | ## Audits and Formal Verification [#audits-and-formal-verification] Morpho is known for its industry-leading security practices and follows a multi-faceted approach to security. Morpho security practices include formal verification, mutation tests, fuzzing, unit testing, and peer reviews that can be found within respective [Github repositories](https://github.com/morpho-org). External measures include professional security reviews, contests, and pre/post-deployment bounties. A whole article was dedicated to the [Morpho Security Framework](https://morpho.org/blog/morpho-blue-security-framework-building-the-most-secure-lending-protocol/). The full list of audits and formal verifications is available below. ### Audits [#audits] | Auditors | Scope | Date | Report | | :------------------------------------------------------- | :--------------------------------------------- | :--------- | :---------------------------------------------------------------------------------------------------- | | [Spearbit](https://spearbit.com/) | Morpho V0 (AaveV3) | 2023-06-19 | [spearbit-report](https://cdn.morpho.org/documents/Spearbit_19062023.pdf) | | [Spearbit](https://spearbit.com/) | Morpho V0 (AaveV3) | 2023-05-04 | [spearbit-report](https://cdn.morpho.org/documents/Spearbit_04052023.pdf) | | [Runtime Verification](https://runtimeverification.com/) | Morpho V0 (AaveV3) | 2023-05-02 | [runtime-verification-report](https://cdn.morpho.org/documents/Runtime_02052023.pdf) | | [Spearbit](https://spearbit.com/) | Morpho V0 | 2023-03-01 | [spearbit-report](https://cdn.morpho.org/documents/Spearbit_010323.pdf) | | [Pessimistic](https://pessimistic.io/) | Morpho V0 | 2023-02-20 | [pessimistic-report](https://cdn.morpho.org/documents/Pessimistic_200223.pdf) | | [Spearbit](https://spearbit.com/) | Morpho V0 \[Draft] | 2022-12-06 | [spearbit-draft-report](https://cdn.morpho.org/documents/Spearbit_morpho_draft_061222.pdf) | | [Pessimistic](https://pessimistic.io/) | Morpho V0 | 2022-10-06 | [pessimistic-report](https://cdn.morpho.org/documents/Pessimistic_061022.pdf) | | [Omniscia](https://omniscia.io/) | Morpho V0 | 2022-08-25 | [omniscia-report](https://omniscia.io/reports/morpho-labs-tokenized-vaults/) | | [Spearbit](https://spearbit.com/) | Morpho V0 (AaveV2) | 2022-08-24 | [spearbit-report](https://cdn.morpho.org/documents/MorphoAaveV2_Audit_Spearbit.pdf) | | [Chainsecurity](https://chainsecurity.com/) | Morpho V0 (AaveV3) \[Non Ethereum] | 2022-08-19 | [chainsecurity-report](https://cdn.morpho.org/documents/MorphoAaveV3_Audit_ChainSecurity.pdf) | | [Omniscia](https://omniscia.io/) | Morpho Datastructures | 2022-08-17 | [omniscia-report](https://cdn.morpho.org/documents/MorphoAaveV2_Audit_Omniscia.pdf) | | [Pessimistic](https://pessimistic.io/) | Morpho V0 (AaveV2) | 2022-07-25 | [pessimistic-report](https://cdn.morpho.org/documents/MorphoAaveV2_Audit_Pessimistic.pdf) | | [Trail of Bits](https://www.trailofbits.com/) | Morpho V0 (CompoundV2) | 2022-07-21 | [trail-of-bits-report](https://cdn.morpho.org/documents/TrailOfBits_210722.pdf) | | [Trail of Bits](https://www.trailofbits.com/) | Morpho V0 (CompoundV2) | 2022-06-03 | [trail-of-bits-report](https://cdn.morpho.org/documents/TrailOfBits_030622.pdf) | | [Spearbit](https://spearbit.com/) | Morpho V0 (AaveV2) | 2022-09-05 | [spearbit-report](https://cdn.morpho.org/documents/Spearbit_090522.pdf) | | [Pessimistic](https://pessimistic.io/) | \[Beta] Morpho V0 (CompoundV2) | 2022-04-25 | [pessimistic-report](https://cdn.morpho.org/documents/Pessimistic_250422.pdf) | | [Pessimistic](https://pessimistic.io/) | \[Beta] Morpho V0 (AaveV2) | 2022-03-08 | [pessimistic-report](https://cdn.morpho.org/documents/Pessimistic_080322.pdf) | | [Pessimistic](https://pessimistic.io/) | \[Beta] Morpho V0 (AaveV2) | 2022-02-16 | [pessimistic-report](https://cdn.morpho.org/documents/pessimistic_160222.pdf) | | [Pessimistic](https://pessimistic.io/) | \[Alpha] Morpho V0 (AaveV2) | 2022-01-11 | [pessimistic-report](https://cdn.morpho.org/documents/Pessimistic_110122.pdf) | | [Solidified](https://www.solidified.io/) | \[Alpha] Morpho V0 (CompoundV2) | 2021-11-04 | [solidified-report](https://cdn.morpho.org/documents/Solidified_041121.pdf) | | [Lexfo](https://www.lexfo.fr/en/) | Morpho-related DNS's & Morpho-related Github's | 2023-05-10 | [Lexfo\_250523.pdf](https://cdn.morpho.org/documents/Lexfo_250523.pdf) | | [Securing](https://www.securing.biz/) | \[Beta] Morpho Association dApp | 2022-04-04 | [MorphoDAO\_Frontend\_04042022.pdf](https://cdn.morpho.org/documents/MorphoDAO_Frontend_04042022.pdf) | ### Formal verifications [#formal-verifications] | Formally Proven | Scope | Date | Tool Used | | :----------------------------------------------------------------------------------------------- | :--------------------------------- | :------------ | :----------------------------------------------------------------------------- | | [Morpho V0 (AaveV3)](https://github.com/morpho-org/morpho-aavev3-optimizer/tree/main/why3) | core logic | April 2023 | [Why3](https://www.why3.org/) | | [Morpho V0](https://github.com/morpho-org/morpho-optimizers/tree/main/certora) | Merkle tree and claim function | April 2024 | [Certora](https://www.certora.com/) & custom checker | | [Morpho utils](https://github.com/morpho-org/morpho-utils/tree/main/certora) | math functions | December 2022 | [Certora](https://www.certora.com/) | | [Morpho data structures](https://github.com/morpho-org/morpho-data-structures/tree/main/certora) | double linked list and log buckets | January 2023 | [Certora](https://www.certora.com/) & [Halmos](https://github.com/a16z/halmos) | --- # MorphoVaultV1Adapter Source: https://docs.morpho.org/developers/contracts/morpho-vault-v1-adapter ## Introduction [#introduction] `MorphoVaultV1Adapter` is an adapter that connects a Morpho Vault V2 to a Morpho Vault V1. It wraps deposits and withdrawals to/from the underlying Vault V1 on behalf of the parent Vault V2. This adapter must be used with Morpho Vaults V1 that are protected against inflation attacks with an initial supply. See [OpenZeppelin ERC4626 inflation attack](https://docs.openzeppelin.com/contracts/5.x/erc4626#inflation-attack) and Inflation Front-Running Attack Protection section [here](/curate/concepts/security-considerations/#inflation-front-running-attack-protection). Must not be used with a Morpho Vault V1 that has a market with an IRM that can re-enter the parent vault or the adapter. Morpho Vaults V1.1 do not realize bad debt, so a Vault V2 supplying through this adapter will not realize the corresponding bad debt either. ## Timelock System [#timelock-system] This adapter has no timelock system. All functions are callable directly by their respective roles with no submit/execute delay. ## Immutables [#immutables] | Name | Type | Description | | :-------------- | :------ | :------------------------------------------------------------------------------------------------------- | | `factory` | address | The factory contract that deployed this adapter. | | `parentVault` | address | The Morpho Vault V2 that this adapter belongs to. | | `morphoVaultV1` | address | The Morpho Vault V1 (MetaMorpho) contract that this adapter deposits into. | | `adapterId` | bytes32 | Unique identifier for this adapter instance, computed as `keccak256(abi.encode("this", address(this)))`. | ## Owner Functions [#owner-functions] ### `setSkimRecipient` [#setskimrecipient] ```solidity function setSkimRecipient(address newSkimRecipient) external ``` Sets the address that receives skimmed token balances. **Callable by:** `Owner` of the parent vault (instant - no timelock) #### Parameters: [#parameters] | Name | Type | Description | | :----------------- | :------ | :------------------------------ | | `newSkimRecipient` | address | The new skim recipient address. | ## Other Functions [#other-functions] ### `skim` [#skim] ```solidity function skim(address token) external ``` Transfers the adapter's entire balance of `token` to `skimRecipient`. Shares of the underlying Morpho Vault V1 (`morphoVaultV1`) cannot be skimmed and will revert if passed as `token`. **Callable by:** `skimRecipient` If `skimRecipient` is not set (`address(0)`), this function will always revert. #### Parameters: [#parameters-1] | Name | Type | Description | | :------ | :------ | :-------------------------------------------------------------- | | `token` | address | The token to skim. Must not be the `morphoVaultV1` share token. | *** ### `allocate` [#allocate] ```solidity function allocate(bytes memory data, uint256 assets, bytes4, address) external returns (bytes32[] memory, int256) ``` Deposits `assets` into the underlying Morpho Vault V1. **Callable by:** `parentVault` #### Parameters: [#parameters-2] | Name | Type | Description | | :------- | :------ | :------------------------------- | | `data` | bytes | Must be empty (`0x`). | | `assets` | uint256 | The amount of assets to deposit. | The `bytes4` and `address` parameters are part of the shared adapter interface but unused by this implementation. #### Return Values: [#return-values] | Name | Type | Description | | :------ | :--------- | :-------------------------------------------------------------- | | `ids` | bytes32\[] | The allocation IDs affected (contains `adapterId`). | | `delta` | int256 | The change in allocation (new allocation minus old allocation). | *** ### `deallocate` [#deallocate] ```solidity function deallocate(bytes memory data, uint256 assets, bytes4, address) external returns (bytes32[] memory, int256) ``` Withdraws `assets` from the underlying Morpho Vault V1 back to the parent vault. **Callable by:** `parentVault` #### Parameters: [#parameters-3] | Name | Type | Description | | :------- | :------ | :-------------------------------- | | `data` | bytes | Must be empty (`0x`). | | `assets` | uint256 | The amount of assets to withdraw. | The `bytes4` and `address` parameters are part of the shared adapter interface but unused by this implementation. #### Return Values: [#return-values-1] | Name | Type | Description | | :------ | :--------- | :-------------------------------------------------------------- | | `ids` | bytes32\[] | The allocation IDs affected (contains `adapterId`). | | `delta` | int256 | The change in allocation (new allocation minus old allocation). | ## View Functions [#view-functions] ### `realAssets` [#realassets] ```solidity function realAssets() external view returns (uint256) ``` Returns the current value of the adapter's Vault V1 share balance in underlying assets. Returns `0` if the parent vault has no recorded allocation for this adapter. #### Return Values: [#return-values-2] | Name | Type | Description | | :------- | :------ | :----------------------------------------------------------------------- | | `assets` | uint256 | The current value of held Vault V1 shares, or `0` if allocation is zero. | *** ### `allocation` [#allocation] ```solidity function allocation() public view returns (uint256) ``` Returns the vault's current allocation for this adapter, as recorded in the parent vault (keyed by `adapterId`). #### Return Values: [#return-values-3] | Name | Type | Description | | :------- | :------ | :------------------------------------------------- | | `assets` | uint256 | The current allocation amount in the parent vault. | *** ### `ids` [#ids] ```solidity function ids() public view returns (bytes32[] memory) ``` Returns the allocation IDs used by this adapter. Contains a single entry: `adapterId`. #### Return Values: [#return-values-4] | Name | Type | Description | | :----- | :--------- | :--------------------------------------------------------------------------- | | `ids_` | bytes32\[] | Array of 1 ID: `adapterId` (`keccak256(abi.encode("this", address(this)))`). | *** ### `skimRecipient` [#skimrecipient] ```solidity address public skimRecipient ``` Returns the address that receives skimmed token balances. #### Return Values: [#return-values-5] | Name | Type | Description | | :---------- | :------ | :---------------------------------- | | `recipient` | address | The current skim recipient address. | --- # Morpho Vaults V2 Source: https://docs.morpho.org/developers/contracts/morpho-vaults-v2 ## Introduction to principles [#introduction-to-principles] By design, Morpho Vault V2 has a non-conventional behaviour on max functions (`maxDeposit`, `maxMint`, `maxWithdraw`, `maxRedeem`): they always return zero. ### Caps structure [#caps-structure] ```solidity struct Caps { uint256 allocation; uint128 absoluteCap; uint128 relativeCap; } ``` | Name | Type | Description | | :------------ | :------ | :----------------------------------------------------------------------------------------- | | `allocation` | uint256 | The current amount of assets allocated to this `id`. | | `absoluteCap` | uint128 | The maximum amount of assets that can be allocated to this `id`. | | `relativeCap` | uint128 | The maximum percentage of total assets that can be allocated to this `id`, scaled by 1e18. | *** ### `id`, `idData`, and `data` [#id-iddata-and-data] Allocation targets are identified by an `id` computed as `keccak256(idData)`, where `idData` is adapter-defined bytes. When allocating or setting caps, you pass the raw `data` bytes. When querying allocations or caps, you must compute and pass the `id` hash. The vault does not provide a helper function to compute `id` - you must calculate it off-chain or in your contract. Refer to each adapter's documentation for the exact `data` encoding and the list of `id`s it registers. Throughout the vault contract, you'll encounter three related concepts for identifying allocation targets: #### `data` / `idData` [#data--iddata] * **Raw bytes**: Protocol-specific data that identifies where assets are allocated * **Encoding varies by adapter**: Each adapter defines what `data` encodes and how many `id`s it registers per allocation (an adapter may register multiple ids, e.g. for per-collateral or per-market caps). Refer to the adapter's documentation for the exact encoding. #### `id` [#id] * **Computed hash**: `id = keccak256(idData)` * **Purpose**: Used as the unique key in the vault's storage mappings to track allocations and caps * **Type**: `bytes32` #### Function usage patterns [#function-usage-patterns] * **Setting caps**: Functions like `increaseAbsoluteCap` take `idData` (raw bytes) as input * **Allocating**: Functions like `allocate` take `data` (raw bytes) as input * **Querying**: Functions like `allocation`, `absoluteCap`, `relativeCap` take `id` (keccak256 hash) as input This design allows the vault to work with any adapter and allocation target, as long as they can be identified by bytes-encoded data. *** ### Function Selectors [#function-selectors] A **function selector** is a `bytes4` value that uniquely identifies a function in a smart contract. It's the first 4 bytes of the `keccak256` hash of the function's signature. **What are they used for in the vault?** Selectors are used for timelock management. The curator can configure different timelock durations for different functions, or permanently abdicate the ability to call specific functions. Functions like `timelock(bytes4 selector)`, `abdicated(bytes4 selector)`, `increaseTimelock(bytes4 selector, ...)`, and `decreaseTimelock(bytes4 selector, ...)` use selectors to identify which function's settings to modify. **Important notes:** * The function signature must include parameter types but not parameter names or return types * Example: `"setPerformanceFee(uint256)"` not `"setPerformanceFee(uint256 newFee)"` * Selectors are always exactly 4 bytes (shown as 10 characters in hex: `0x` + 8 hex digits) #### Timelocked Functions and Selectors [#timelocked-functions-and-selectors] | Function Name | Selector | | :--------------------------- | :----------- | | `setAdapterRegistry` | `0x5b34b823` | | `abdicate` | `0xb2e32848` | | `setReceiveSharesGate` | `0x2cb19f98` | | `setSendSharesGate` | `0xc21ad028` | | `setReceiveAssetsGate` | `0x04dbf0ce` | | `addAdapter` | `0x60d54d41` | | `increaseAbsoluteCap` | `0xf6f98fd5` | | `increaseRelativeCap` | `0x2438525b` | | `setForceDeallocatePenalty` | `0x3e9d2ac7` | | `setIsAllocator` | `0xb192a84a` | | `setSendAssetsGate` | `0x871c979c` | | `setPerformanceFee` | `0x70897b23` | | `setManagementFee` | `0xfe56e232` | | `setPerformanceFeeRecipient` | `0x6a5f1aa2` | | `setManagementFeeRecipient` | `0x9faae464` | | `removeAdapter` | `0x585cd34b` | | `increaseTimelock` | `0x47966291` | | `decreaseTimelock` | `0x5c1a1a4f` | ## External Functions [#external-functions] ### `deposit` [#deposit] ```solidity function deposit(uint256 assets, address onBehalf) external returns (uint256) ``` Deposits `assets` of the underlying token to mint vault shares for `onBehalf`. **Callable by:** Anyone #### Parameters: [#parameters] | Name | Type | Description | | :--------- | :------ | :--------------------------------------------- | | `assets` | uint256 | The amount of the underlying asset to deposit. | | `onBehalf` | address | The recipient of the minted vault shares. | #### Return Values: [#return-values] | Name | Type | Description | | :------- | :------ | :--------------------------------- | | `shares` | uint256 | The amount of vault shares minted. | *** ### `mint` [#mint] ```solidity function mint(uint256 shares, address onBehalf) external returns (uint256) ``` Mints exactly `shares` vault shares for `onBehalf` by depositing the required amount of the underlying token. **Callable by:** Anyone #### Parameters: [#parameters-1] | Name | Type | Description | | :--------- | :------ | :---------------------------------------- | | `shares` | uint256 | The exact amount of shares to mint. | | `onBehalf` | address | The recipient of the minted vault shares. | #### Return Values: [#return-values-1] | Name | Type | Description | | :------- | :------ | :-------------------------------------------- | | `assets` | uint256 | The amount of the underlying asset deposited. | *** ### `withdraw` [#withdraw] ```solidity function withdraw(uint256 assets, address receiver, address onBehalf) public returns (uint256) ``` Withdraws `assets` of the underlying token by burning shares from `onBehalf` and sends them to `receiver`. **Callable by:** `onBehalf` or approved address #### Parameters: [#parameters-2] | Name | Type | Description | | :--------- | :------ | :------------------------------------------------------------------- | | `assets` | uint256 | The amount of the underlying asset to withdraw. | | `receiver` | address | The recipient of the withdrawn assets. | | `onBehalf` | address | The owner of the shares to be burned. `msg.sender` must be approved. | #### Return Values: [#return-values-2] | Name | Type | Description | | :------- | :------ | :--------------------------------- | | `shares` | uint256 | The amount of vault shares burned. | *** ### `redeem` [#redeem] ```solidity function redeem(uint256 shares, address receiver, address onBehalf) external returns (uint256) ``` Burns exactly `shares` vault shares from `onBehalf` and sends the corresponding amount of underlying assets to `receiver`. **Callable by:** `onBehalf` or approved address #### Parameters: [#parameters-3] | Name | Type | Description | | :--------- | :------ | :------------------------------------------------------------------- | | `shares` | uint256 | The exact amount of shares to burn. | | `receiver` | address | The recipient of the withdrawn assets. | | `onBehalf` | address | The owner of the shares to be burned. `msg.sender` must be approved. | #### Return Values: [#return-values-3] | Name | Type | Description | | :------- | :------ | :-------------------------------------------- | | `assets` | uint256 | The amount of the underlying asset withdrawn. | *** ### `forceDeallocate` [#forcedeallocate] ```solidity function forceDeallocate(address adapter, bytes memory data, uint256 assets, address onBehalf) external returns (uint256) ``` Forcibly deallocates `assets` from a specific `adapter` to the vault's idle pool. A penalty can be set and taken from `onBehalf`'s shares to disincentivize misuse (see [`setForceDeallocatePenalty`](/get-started/resources/contracts/morpho-vaults-v2/#setforcedeallocatepenalty)). This function is permissionless and serves as an in-kind redemption mechanism. **Callable by:** Anyone A `forceDeallocatePenalty` of 0 allows anyone to call `forceDeallocate` on behalf of any user. It would not lead to loss of assets, but could be used to cause vault assets to become idle at times unintended by the curator. #### Parameters: [#parameters-4] | Name | Type | Description | | :--------- | :------ | :-------------------------------------------------------- | | `adapter` | address | The adapter to deallocate from. | | `data` | bytes | The protocol-specific data for the deallocation. | | `assets` | uint256 | The amount of assets to deallocate. | | `onBehalf` | address | The account from which the penalty shares will be burned. | #### Return Values: [#return-values-4] | Name | Type | Description | | :---------------- | :------ | :-------------------------------------------------------- | | `withdrawnShares` | uint256 | The amount of shares burned from `onBehalf` as a penalty. | *** ### `accrueInterest` [#accrueinterest] ```solidity function accrueInterest() public ``` Accrues interest for the vault by querying adapters for their current assets and updating `totalAssets`. This function is called by most state-changing functions. **Callable by:** Anyone *** ### `accrueInterestView` [#accrueinterestview] ```solidity function accrueInterestView() public view returns (uint256, uint256, uint256) ``` Returns the view version of interest accrual without updating state. #### Return Values: [#return-values-5] | Name | Type | Description | | :--------------------- | :------ | :------------------------------------------------------------ | | `newTotalAssets` | uint256 | The updated total assets after interest accrual. | | `performanceFeeShares` | uint256 | The amount of shares that would be minted as performance fee. | | `managementFeeShares` | uint256 | The amount of shares that would be minted as management fee. | *** ### `previewDeposit` [#previewdeposit] ```solidity function previewDeposit(uint256 assets) public view returns (uint256) ``` Returns previewed minted shares for a given asset amount. #### Parameters: [#parameters-5] | Name | Type | Description | | :------- | :------ | :------------------------------------------- | | `assets` | uint256 | The amount of assets to preview deposit for. | #### Return Values: [#return-values-6] | Name | Type | Description | | :------- | :------ | :----------------------------------------- | | `shares` | uint256 | The amount of shares that would be minted. | *** ### `previewMint` [#previewmint] ```solidity function previewMint(uint256 shares) public view returns (uint256) ``` Returns previewed deposited assets for a given share amount. #### Parameters: [#parameters-6] | Name | Type | Description | | :------- | :------ | :---------------------------------------- | | `shares` | uint256 | The amount of shares to preview mint for. | #### Return Values: [#return-values-7] | Name | Type | Description | | :------- | :------ | :-------------------------------------------- | | `assets` | uint256 | The amount of assets that would be deposited. | *** ### `previewWithdraw` [#previewwithdraw] ```solidity function previewWithdraw(uint256 assets) public view returns (uint256) ``` Returns previewed redeemed shares for a given asset amount. #### Parameters: [#parameters-7] | Name | Type | Description | | :------- | :------ | :---------------------------------------------- | | `assets` | uint256 | The amount of assets to preview withdrawal for. | #### Return Values: [#return-values-8] | Name | Type | Description | | :------- | :------ | :----------------------------------------- | | `shares` | uint256 | The amount of shares that would be burned. | *** ### `previewRedeem` [#previewredeem] ```solidity function previewRedeem(uint256 shares) public view returns (uint256) ``` Returns previewed withdrawn assets for a given share amount. #### Parameters: [#parameters-8] | Name | Type | Description | | :------- | :------ | :---------------------------------------------- | | `shares` | uint256 | The amount of shares to preview redemption for. | #### Return Values: [#return-values-9] | Name | Type | Description | | :------- | :------ | :-------------------------------------------- | | `assets` | uint256 | The amount of assets that would be withdrawn. | *** ### `convertToShares` [#converttoshares] ```solidity function convertToShares(uint256 assets) external view returns (uint256) ``` Returns corresponding shares (rounded down) for a given asset amount. Takes into account performance and management fees. #### Parameters: [#parameters-9] | Name | Type | Description | | :------- | :------ | :------------------------------- | | `assets` | uint256 | The amount of assets to convert. | #### Return Values: [#return-values-10] | Name | Type | Description | | :------- | :------ | :---------------------------------- | | `shares` | uint256 | The corresponding amount of shares. | *** ### `convertToAssets` [#converttoassets] ```solidity function convertToAssets(uint256 shares) external view returns (uint256) ``` Returns corresponding assets (rounded down) for a given share amount. Takes into account performance and management fees. #### Parameters: [#parameters-10] | Name | Type | Description | | :------- | :------ | :------------------------------- | | `shares` | uint256 | The amount of shares to convert. | #### Return Values: [#return-values-11] | Name | Type | Description | | :------- | :------ | :---------------------------------- | | `assets` | uint256 | The corresponding amount of assets. | *** ### `maxDeposit` [#maxdeposit] ```solidity function maxDeposit(address) external pure returns (uint256) ``` Returns the maximum amount of assets that can be deposited. Always returns 0 as a conservative underestimate. Morpho Vault V2 can use external gate contracts for access control; because gate behavior cannot be guaranteed to be revert-free, computing a reliable maximum is not possible. *** ### `maxMint` [#maxmint] ```solidity function maxMint(address) external pure returns (uint256) ``` Returns the maximum amount of shares that can be minted. Always returns 0 as a conservative underestimate. Morpho Vault V2 can use external gate contracts for access control; because gate behavior cannot be guaranteed to be revert-free, computing a reliable maximum is not possible. *** ### `maxWithdraw` [#maxwithdraw] ```solidity function maxWithdraw(address) external pure returns (uint256) ``` Returns the maximum amount of assets that can be withdrawn. Always returns 0 as a conservative underestimate. Morpho Vault V2 can use external gate contracts for access control; because gate behavior cannot be guaranteed to be revert-free, computing a reliable maximum is not possible. *** ### `maxRedeem` [#maxredeem] ```solidity function maxRedeem(address) external pure returns (uint256) ``` Returns the maximum amount of shares that can be redeemed. Always returns 0 as a conservative underestimate. Morpho Vault V2 can use external gate contracts for access control; because gate behavior cannot be guaranteed to be revert-free, computing a reliable maximum is not possible. *** ### `multicall` [#multicall] ```solidity function multicall(bytes[] calldata data) external ``` Useful for EOAs to batch admin calls. Does not return anything, because accounts who would use the return data would be contracts, which can do the multicall themselves. **Callable by:** Anyone #### Parameters: [#parameters-11] | Name | Type | Description | | :----- | :------- | :------------------------------------------ | | `data` | bytes\[] | Array of encoded function calls to execute. | *** As a reminder, there are four distinct [roles](/curate/concepts/roles#morpho-vaults-v2-roles) in Morpho Vaults V2: 1. **Owner**: Manages role assignments. 2. **Curator**: The chief risk curator and strategist. 3. **Allocator**: The active portfolio curator. 4. **Sentinel**: A safety-oriented role for reactive risk mitigation. ## Owner Functions [#owner-functions] *** ### `setOwner` [#setowner] ```solidity function setOwner(address newOwner) external ``` Transfers ownership of the vault to `newOwner`. **Callable by:** `Owner` *** ### `setCurator` [#setcurator] ```solidity function setCurator(address newCurator) external ``` Sets the vault's `Curator`. **Callable by:** `Owner` *** ### `setIsSentinel` [#setissentinel] ```solidity function setIsSentinel(address account, bool newIsSentinel) external ``` Grants or revokes the `Sentinel` role for an `account`. **Callable by:** `Owner` *** ### `setName` [#setname] ```solidity function setName(string memory newName) external ``` Sets the vault's name. **Callable by:** `Owner` *** ### `setSymbol` [#setsymbol] ```solidity function setSymbol(string memory newSymbol) external ``` Sets the vault's symbol. **Callable by:** `Owner` ## Curator Functions [#curator-functions] The following functions are only callable by the `Curator`. Some are subject to a **timelock** while others are **instant**. The timelock process is: 1. `Curator` calls `submit(bytes calldata data)` with the ABI-encoded function call. 2. After the timelock expires, anyone can execute the function directly. *** ### `abdicate` [#abdicate] ```solidity function abdicate(bytes4 selector) external ``` Irreversibly disables the ability to execute a specific type of action. This action is irreversible. Existing pending operations submitted before abdicating cannot be executed. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0xb2e32848` #### Parameters: [#parameters-12] | Name | Type | Description | | :--------- | :----- | :--------------------------------- | | `selector` | bytes4 | The function selector to abdicate. | *** ### `addAdapter` [#addadapter] ```solidity function addAdapter(address account) external ``` Adds a new adapter to the vault. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x60d54d41` #### Parameters: [#parameters-13] | Name | Type | Description | | :-------- | :------ | :-------------------------- | | `account` | address | The adapter address to add. | *** ### `decreaseAbsoluteCap` [#decreaseabsolutecap] ```solidity function decreaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external ``` Decreases the absolute cap for an allocation target. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. **Callable by:** `Curator`, `Sentinel` #### Parameters: [#parameters-14] | Name | Type | Description | | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `idData` | bytes | Market-specific data identifying the allocation target (e.g., market ID for Morpho markets). The hash of this data (`keccak256(idData)`) is used as the unique identifier. | | `newAbsoluteCap` | uint256 | The new absolute cap value. | *** ### `decreaseRelativeCap` [#decreaserelativecap] ```solidity function decreaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external ``` Decreases the relative cap for an allocation target. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. **Callable by:** `Curator`, `Sentinel` #### Parameters: [#parameters-15] | Name | Type | Description | | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `idData` | bytes | Market-specific data identifying the allocation target (e.g., market ID for Morpho markets). The hash of this data (`keccak256(idData)`) is used as the unique identifier. | | `newRelativeCap` | uint256 | The new relative cap value (scaled by 1e18). | *** ### `decreaseTimelock` [#decreasetimelock] ```solidity function decreaseTimelock(bytes4 selector, uint256 newDuration) external ``` Decreases a function's timelock duration. This action's timelock equals the current timelock of the function being decreased. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x5c1a1a4f` #### Parameters: [#parameters-16] | Name | Type | Description | | :------------ | :------ | :------------------------- | | `selector` | bytes4 | The function selector. | | `newDuration` | uint256 | The new timelock duration. | *** ### `increaseAbsoluteCap` [#increaseabsolutecap] ```solidity function increaseAbsoluteCap(bytes memory idData, uint256 newAbsoluteCap) external ``` Increases the absolute cap for an allocation target. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0xf6f98fd5` #### Parameters: [#parameters-17] | Name | Type | Description | | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `idData` | bytes | Market-specific data identifying the allocation target (e.g., market ID for Morpho markets). The hash of this data (`keccak256(idData)`) is used as the unique identifier. | | `newAbsoluteCap` | uint256 | The new absolute cap value. | *** ### `increaseRelativeCap` [#increaserelativecap] ```solidity function increaseRelativeCap(bytes memory idData, uint256 newRelativeCap) external ``` Increases the relative cap for an allocation target. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x2438525b` #### Parameters: [#parameters-18] | Name | Type | Description | | :--------------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `idData` | bytes | Market-specific data identifying the allocation target (e.g., market ID for Morpho markets). The hash of this data (`keccak256(idData)`) is used as the unique identifier. | | `newRelativeCap` | uint256 | The new relative cap value (scaled by 1e18). | *** ### `increaseTimelock` [#increasetimelock] ```solidity function increaseTimelock(bytes4 selector, uint256 newDuration) external ``` Increases a function's timelock duration. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x47966291` #### Parameters: [#parameters-19] | Name | Type | Description | | :------------ | :------ | :------------------------- | | `selector` | bytes4 | The function selector. | | `newDuration` | uint256 | The new timelock duration. | *** ### `removeAdapter` [#removeadapter] ```solidity function removeAdapter(address account) external ``` Removes an adapter from the vault. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x585cd34b` #### Parameters: [#parameters-20] | Name | Type | Description | | :-------- | :------ | :----------------------------- | | `account` | address | The adapter address to remove. | *** ### `revoke` [#revoke] ```solidity function revoke(bytes calldata data) external ``` Revokes a pending timelocked action. **Callable by:** `Curator`, `Sentinel` #### Parameters: [#parameters-21] | Name | Type | Description | | :----- | :---- | :-------------------------------------------- | | `data` | bytes | The ABI-encoded function call data to revoke. | *** ### `setAdapterRegistry` [#setadapterregistry] ```solidity function setAdapterRegistry(address newAdapterRegistry) external ``` Sets the adapter registry contract. The function validates that the new registry approves all currently added adapters. If the new registry returns false for any existing adapter, the transaction will revert. This ensures that changing the registry doesn't invalidate adapters that are already in use. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x5b34b823` #### Parameters: [#parameters-22] | Name | Type | Description | | :------------------- | :------ | :--------------------------------- | | `newAdapterRegistry` | address | The new adapter registry contract. | *** ### `setForceDeallocatePenalty` [#setforcedeallocatepenalty] ```solidity function setForceDeallocatePenalty(address adapter, uint256 newForceDeallocatePenalty) external ``` Sets the penalty for using [`forceDeallocate`](/get-started/resources/contracts/morpho-vaults-v2/#forcedeallocate) on a specific adapter. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x3e9d2ac7` #### Parameters: [#parameters-23] | Name | Type | Description | | :-------------------------- | :------ | :-------------------------------------- | | `adapter` | address | The adapter to set penalty for. | | `newForceDeallocatePenalty` | uint256 | The new penalty value (scaled by 1e18). | *** ### `setIsAllocator` [#setisallocator] ```solidity function setIsAllocator(address account, bool newIsAllocator) external ``` Grants or revokes the `Allocator` role for an account. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0xb192a84a` #### Parameters: [#parameters-24] | Name | Type | Description | | :--------------- | :------ | :------------------------------------------ | | `account` | address | The account to modify. | | `newIsAllocator` | bool | Whether the account should be an allocator. | *** ### `setManagementFee` [#setmanagementfee] ```solidity function setManagementFee(uint256 newManagementFee) external ``` Sets the vault's management fee. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0xfe56e232` #### Parameters: [#parameters-25] | Name | Type | Description | | :----------------- | :------ | :--------------------------------------- | | `newManagementFee` | uint256 | The new management fee (scaled by 1e18). | *** ### `setManagementFeeRecipient` [#setmanagementfeerecipient] ```solidity function setManagementFeeRecipient(address newManagementFeeRecipient) external ``` Sets the management fee recipient. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x9faae464` #### Parameters: [#parameters-26] | Name | Type | Description | | :-------------------------- | :------ | :-------------------------------- | | `newManagementFeeRecipient` | address | The new management fee recipient. | Fees are minted as shares. To receive the underlying asset, the fee recipient must call `redeem()` at some point, either directly, or by granting allowance to another address that redeems on their behalf. **The fee recipient must be an address capable of interacting with the vault** (EOA, smart wallet, multisig, etc.). A fully passive address that never interacts with the chain won't be able to actually receive the fee in the underlying asset. *** ### `setPerformanceFee` [#setperformancefee] ```solidity function setPerformanceFee(uint256 newPerformanceFee) external ``` Sets the vault's performance fee. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x70897b23` #### Parameters: [#parameters-27] | Name | Type | Description | | :------------------ | :------ | :---------------------------------------- | | `newPerformanceFee` | uint256 | The new performance fee (scaled by 1e18). | *** ### `setPerformanceFeeRecipient` [#setperformancefeerecipient] ```solidity function setPerformanceFeeRecipient(address newPerformanceFeeRecipient) external ``` Sets the performance fee recipient. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x6a5f1aa2` #### Parameters: [#parameters-28] | Name | Type | Description | | :--------------------------- | :------ | :--------------------------------- | | `newPerformanceFeeRecipient` | address | The new performance fee recipient. | Fees are minted as shares. To receive the underlying asset, the fee recipient must call `redeem()` at some point, either directly, or by granting allowance to another address that redeems on their behalf. **The fee recipient must be an address capable of interacting with the vault** (EOA, smart wallet, multisig, etc.). A fully passive address that never interacts with the chain won't be able to actually receive the fee in the underlying asset. *** ### `setReceiveAssetsGate` [#setreceiveassetsgate] ```solidity function setReceiveAssetsGate(address newReceiveAssetsGate) external ``` Sets the contract for gating asset receipts. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x04dbf0ce` #### Parameters: [#parameters-29] | Name | Type | Description | | :--------------------- | :------ | :------------------------------------ | | `newReceiveAssetsGate` | address | The new receive assets gate contract. | *** ### `setReceiveSharesGate` [#setreceivesharesgate] ```solidity function setReceiveSharesGate(address newReceiveSharesGate) external ``` Sets the contract for gating share receipts. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x2cb19f98` #### Parameters: [#parameters-30] | Name | Type | Description | | :--------------------- | :------ | :------------------------------------ | | `newReceiveSharesGate` | address | The new receive shares gate contract. | *** ### `setSendAssetsGate` [#setsendassetsgate] ```solidity function setSendAssetsGate(address newSendAssetsGate) external ``` Sets the contract for gating asset sends. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0x871c979c` #### Parameters: [#parameters-31] | Name | Type | Description | | :------------------ | :------ | :--------------------------------- | | `newSendAssetsGate` | address | The new send assets gate contract. | *** ### `setSendSharesGate` [#setsendsharesgate] ```solidity function setSendSharesGate(address newSendSharesGate) external ``` Sets the contract for gating share sends. **Callable by:** `Curator` (timelocked - submit first, then anyone can execute after delay) Selector: `0xc21ad028` #### Parameters: [#parameters-32] | Name | Type | Description | | :------------------ | :------ | :--------------------------------- | | `newSendSharesGate` | address | The new send shares gate contract. | *** ### `submit` [#submit] ```solidity function submit(bytes calldata data) external ``` Submits a timelocked action for execution after the timelock period expires. **Callable by:** `Curator` #### Parameters: [#parameters-33] | Name | Type | Description | | :----- | :---- | :---------------------------------- | | `data` | bytes | The ABI-encoded function call data. | ## Allocator Functions [#allocator-functions] ### `allocate` [#allocate] ```solidity function allocate(address adapter, bytes memory data, uint256 assets) external ``` Allocates `assets` from the vault's idle pool to a specific `adapter`. The allocation is subject to the caps configured for the target market. **Callable by:** `Allocator` #### Parameters: [#parameters-34] | Name | Type | Description | | :-------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `adapter` | address | The adapter to allocate assets to. | | `data` | bytes | Market-specific data encoding passed to the adapter (e.g., market ID for Morpho markets). The hash of this data (`keccak256(data)`) identifies the allocation target for cap enforcement. | | `assets` | uint256 | The amount of assets to allocate. | *** ### `deallocate` [#deallocate] ```solidity function deallocate(address adapter, bytes memory data, uint256 assets) external ``` Deallocates `assets` from an `adapter` back to the vault's idle pool. **Callable by:** `Allocator`, `Sentinel` #### Parameters: [#parameters-35] | Name | Type | Description | | :-------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------- | | `adapter` | address | The adapter to deallocate assets from. | | `data` | bytes | Market-specific data encoding passed to the adapter (e.g., market ID for Morpho markets). Must match the data used when allocating. | | `assets` | uint256 | The amount of assets to deallocate. | *** ### `setLiquidityAdapterAndData` [#setliquidityadapteranddata] ```solidity function setLiquidityAdapterAndData(address newLiquidityAdapter, bytes memory newLiquidityData) external ``` Sets the default adapter for handling user deposits and withdrawals. **Callable by:** `Allocator` #### Parameters: [#parameters-36] | Name | Type | Description | | :-------------------- | :------ | :-------------------------------------- | | `newLiquidityAdapter` | address | The new liquidity adapter address. | | `newLiquidityData` | bytes | The new liquidity data for the adapter. | *** ### `setMaxRate` [#setmaxrate] ```solidity function setMaxRate(uint256 newMaxRate) external ``` Sets the maximum rate at which `totalAssets` can grow. An allocator-controlled parameter that caps how quickly totalAssets can grow, useful for implementing fixed-rate distributions or preventing unrealistic yield spikes. `maxRate` is capped at 200% APR (see `MAX_MAX_RATE` constant [here](https://github.com/morpho-org/vault-v2/blob/00881eb5f5c8031252c7fcdb7cd7e026bb21cfa4/src/libraries/ConstantsLib.sol#L9)) **Callable by:** `Allocator` If `maxRate` is 0, yield does not accrue to vault's suppliers. #### Parameters: [#parameters-37] | Name | Type | Description | | :----------- | :------ | :------------------------------------------------ | | `newMaxRate` | uint256 | The new maximum rate per second (scaled by 1e18). | ## Gating Functions [#gating-functions] Gates are optional external smart contracts that implement access control for the vault. The vault supports four independent gates: `receiveSharesGate`, `sendSharesGate`, `receiveAssetsGate`, and `sendAssetsGate`. Gates are separate smart contracts that you (or the curator) must deploy independently. They are not part of the vault contract itself. The curator configures them using functions like `setReceiveSharesGate`, `setSendSharesGate`, etc. Each gate contract must implement a function that returns `bool` to indicate whether an account is allowed to perform the action. The vault calls these gates during transfers and deposits/withdrawals to enforce access control. *** ### `canReceiveShares` [#canreceiveshares] ```solidity function canReceiveShares(address account) public view returns (bool) ``` Checks if an account can receive shares based on the receive shares gate. #### Parameters: [#parameters-38] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-12] | Name | Type | Description | | :-------- | :--- | :-------------------------------------- | | `allowed` | bool | Whether the account can receive shares. | *** ### `canSendShares` [#cansendshares] ```solidity function canSendShares(address account) public view returns (bool) ``` Checks if an account can send shares based on the send shares gate. #### Parameters: [#parameters-39] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-13] | Name | Type | Description | | :-------- | :--- | :----------------------------------- | | `allowed` | bool | Whether the account can send shares. | *** ### `canReceiveAssets` [#canreceiveassets] ```solidity function canReceiveAssets(address account) public view returns (bool) ``` Checks if an account can receive assets based on the receive assets gate. The vault itself is always allowed. #### Parameters: [#parameters-40] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-14] | Name | Type | Description | | :-------- | :--- | :-------------------------------------- | | `allowed` | bool | Whether the account can receive assets. | *** ### `canSendAssets` [#cansendassets] ```solidity function canSendAssets(address account) public view returns (bool) ``` Checks if an account can send assets based on the send assets gate. #### Parameters: [#parameters-41] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-15] | Name | Type | Description | | :-------- | :--- | :----------------------------------- | | `allowed` | bool | Whether the account can send assets. | ## View Functions [#view-functions] ### `totalAssets` [#totalassets] ```solidity function totalAssets() external view returns (uint256) ``` Returns the total amount of underlying assets held by the vault, including both idle assets and assets allocated to adapters. #### Return Values: [#return-values-16] | Name | Type | Description | | :------- | :------ | :----------------------------- | | `assets` | uint256 | The total assets in the vault. | *** ### `adaptersLength` [#adapterslength] ```solidity function adaptersLength() external view returns (uint256) ``` Returns the number of adapters currently added to the vault. #### Return Values: [#return-values-17] | Name | Type | Description | | :------- | :------ | :---------------------- | | `length` | uint256 | The number of adapters. | *** ### `absoluteCap` [#absolutecap] ```solidity function absoluteCap(bytes32 id) external view returns (uint256) ``` Returns the absolute cap for a given allocation `id`. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. #### Parameters: [#parameters-42] | Name | Type | Description | | :--- | :------ | :---------------------------------------------------- | | `id` | bytes32 | The allocation identifier (keccak256 hash of idData). | #### Return Values: [#return-values-18] | Name | Type | Description | | :---- | :------ | :---------------------- | | `cap` | uint256 | The absolute cap value. | *** ### `relativeCap` [#relativecap] ```solidity function relativeCap(bytes32 id) external view returns (uint256) ``` Returns the relative cap for a given allocation `id`. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. #### Parameters: [#parameters-43] | Name | Type | Description | | :--- | :------ | :---------------------------------------------------- | | `id` | bytes32 | The allocation identifier (keccak256 hash of idData). | #### Return Values: [#return-values-19] | Name | Type | Description | | :---- | :------ | :--------------------------------------- | | `cap` | uint256 | The relative cap value (scaled by 1e18). | *** ### `allocation` [#allocation] ```solidity function allocation(bytes32 id) external view returns (uint256) ``` Returns the current allocation amount for a given `id`. The `id` is computed as `keccak256(idData)` where `idData` is the market-specific data identifying the allocation target. #### Parameters: [#parameters-44] | Name | Type | Description | | :--- | :------ | :---------------------------------------------------- | | `id` | bytes32 | The allocation identifier (keccak256 hash of idData). | #### Return Values: [#return-values-20] | Name | Type | Description | | :------- | :------ | :----------------------------- | | `amount` | uint256 | The current allocation amount. | *** ### `owner` [#owner] ```solidity function owner() external view returns (address) ``` Returns the current owner address of the vault. #### Return Values: [#return-values-21] | Name | Type | Description | | :------ | :------ | :----------------- | | `owner` | address | The owner address. | *** ### `curator` [#curator] ```solidity function curator() external view returns (address) ``` Returns the current curator address of the vault. #### Return Values: [#return-values-22] | Name | Type | Description | | :-------- | :------ | :------------------- | | `curator` | address | The curator address. | *** ### `isSentinel` [#issentinel] ```solidity function isSentinel(address account) external view returns (bool) ``` Checks if an account has the Sentinel role. #### Parameters: [#parameters-45] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-23] | Name | Type | Description | | :----------- | :--- | :--------------------------------- | | `isSentinel` | bool | Whether the account is a sentinel. | *** ### `isAllocator` [#isallocator] ```solidity function isAllocator(address account) external view returns (bool) ``` Checks if an account has the Allocator role. #### Parameters: [#parameters-46] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-24] | Name | Type | Description | | :------------ | :--- | :----------------------------------- | | `isAllocator` | bool | Whether the account is an allocator. | *** ### `receiveSharesGate` [#receivesharesgate] ```solidity function receiveSharesGate() external view returns (address) ``` Returns the address of the receive shares gate contract. #### Return Values: [#return-values-25] | Name | Type | Description | | :----- | :------ | :------------------------------- | | `gate` | address | The receive shares gate address. | *** ### `sendSharesGate` [#sendsharesgate] ```solidity function sendSharesGate() external view returns (address) ``` Returns the address of the send shares gate contract. #### Return Values: [#return-values-26] | Name | Type | Description | | :----- | :------ | :---------------------------- | | `gate` | address | The send shares gate address. | *** ### `receiveAssetsGate` [#receiveassetsgate] ```solidity function receiveAssetsGate() external view returns (address) ``` Returns the address of the receive assets gate contract. #### Return Values: [#return-values-27] | Name | Type | Description | | :----- | :------ | :------------------------------- | | `gate` | address | The receive assets gate address. | *** ### `sendAssetsGate` [#sendassetsgate] ```solidity function sendAssetsGate() external view returns (address) ``` Returns the address of the send assets gate contract. #### Return Values: [#return-values-28] | Name | Type | Description | | :----- | :------ | :---------------------------- | | `gate` | address | The send assets gate address. | *** ### `adapterRegistry` [#adapterregistry] ```solidity function adapterRegistry() external view returns (address) ``` Returns the address of the adapter registry contract. #### Return Values: [#return-values-29] | Name | Type | Description | | :--------- | :------ | :---------------------------- | | `registry` | address | The adapter registry address. | *** ### `adapters` [#adapters] ```solidity function adapters(uint256 index) external view returns (address) ``` Returns the adapter address at a specific index (index list retrievable via the [`adaptersLength`](/get-started/resources/contracts/morpho-vaults-v2/#adapterslength) function). #### Parameters: [#parameters-47] | Name | Type | Description | | :------ | :------ | :------------------------------- | | `index` | uint256 | The index in the adapters array. | #### Return Values: [#return-values-30] | Name | Type | Description | | :-------- | :------ | :-------------------------------------- | | `adapter` | address | The adapter address at the given index. | *** ### `isAdapter` [#isadapter] ```solidity function isAdapter(address account) external view returns (bool) ``` Checks if an account is a registered adapter. #### Parameters: [#parameters-48] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `account` | address | The account to check. | #### Return Values: [#return-values-31] | Name | Type | Description | | :---------- | :--- | :--------------------------------- | | `isAdapter` | bool | Whether the account is an adapter. | *** ### `liquidityAdapter` [#liquidityadapter] ```solidity function liquidityAdapter() external view returns (address) ``` Returns the default adapter used for handling user deposits and withdrawals. #### Return Values: [#return-values-32] | Name | Type | Description | | :-------- | :------ | :----------------------------- | | `adapter` | address | The liquidity adapter address. | *** ### `liquidityData` [#liquiditydata] ```solidity function liquidityData() external view returns (bytes memory) ``` Returns the data associated with the liquidity adapter. #### Return Values: [#return-values-33] | Name | Type | Description | | :----- | :---- | :-------------------------- | | `data` | bytes | The liquidity adapter data. | *** ### `forceDeallocatePenalty` [#forcedeallocatepenalty] ```solidity function forceDeallocatePenalty(address adapter) external view returns (uint256) ``` Returns the penalty for using `forceDeallocate` on a specific adapter. #### Parameters: [#parameters-49] | Name | Type | Description | | :-------- | :------ | :-------------------- | | `adapter` | address | The adapter to check. | #### Return Values: [#return-values-34] | Name | Type | Description | | :-------- | :------ | :--------------------------------------------- | | `penalty` | uint256 | The force deallocate penalty (scaled by 1e18). | *** ### `virtualShares` [#virtualshares] ```solidity function virtualShares() external view returns (uint256) ``` Returns the virtual shares used for avoiding initial share inflation attacks. #### Return Values: [#return-values-35] | Name | Type | Description | | :------- | :------ | :------------------------- | | `shares` | uint256 | The virtual shares amount. | *** ### `firstTotalAssets` [#firsttotalassets] ```solidity function firstTotalAssets() external view returns (uint256) ``` Returns the total assets after the first interest accrual of the current transaction. Used to prevent bypassing relative caps with flashloans. #### Return Values: [#return-values-36] | Name | Type | Description | | :------- | :------ | :-------------------------------------------------------------------- | | `assets` | uint256 | The total assets after the first interest accrual of the transaction. | *** ### `_totalAssets` [#_totalassets] ```solidity function _totalAssets() external view returns (uint128) ``` Returns the internal cached total assets value. #### Return Values: [#return-values-37] | Name | Type | Description | | :------- | :------ | :----------------------- | | `assets` | uint128 | The cached total assets. | *** ### `lastUpdate` [#lastupdate] ```solidity function lastUpdate() external view returns (uint64) ``` Returns the timestamp of the last interest accrual. #### Return Values: [#return-values-38] | Name | Type | Description | | :---------- | :----- | :------------------------- | | `timestamp` | uint64 | The last update timestamp. | *** ### `maxRate` [#maxrate] ```solidity function maxRate() external view returns (uint64) ``` Returns the maximum rate at which `totalAssets` can grow. #### Return Values: [#return-values-39] | Name | Type | Description | | :----- | :----- | :-------------------------------------------- | | `rate` | uint64 | The maximum rate per second (scaled by 1e18). | *** ### `performanceFee` [#performancefee] ```solidity function performanceFee() external view returns (uint96) ``` Returns the current performance fee. #### Return Values: [#return-values-40] | Name | Type | Description | | :---- | :----- | :------------------------------------ | | `fee` | uint96 | The performance fee (scaled by 1e18). | *** ### `performanceFeeRecipient` [#performancefeerecipient] ```solidity function performanceFeeRecipient() external view returns (address) ``` Returns the recipient address of performance fees. #### Return Values: [#return-values-41] | Name | Type | Description | | :---------- | :------ | :------------------------------------- | | `recipient` | address | The performance fee recipient address. | *** ### `managementFee` [#managementfee] ```solidity function managementFee() external view returns (uint96) ``` Returns the current management fee. #### Return Values: [#return-values-42] | Name | Type | Description | | :---- | :----- | :----------------------------------- | | `fee` | uint96 | The management fee (scaled by 1e18). | *** ### `managementFeeRecipient` [#managementfeerecipient] ```solidity function managementFeeRecipient() external view returns (address) ``` Returns the recipient address of management fees. #### Return Values: [#return-values-43] | Name | Type | Description | | :---------- | :------ | :------------------------------------ | | `recipient` | address | The management fee recipient address. | *** ### `timelock` [#timelock] ```solidity function timelock(bytes4 selector) external view returns (uint256) ``` Returns the timelock duration for a specific function selector. #### Parameters: [#parameters-50] | Name | Type | Description | | :--------- | :----- | :--------------------- | | `selector` | bytes4 | The function selector. | #### Return Values: [#return-values-44] | Name | Type | Description | | :--------- | :------ | :-------------------------------- | | `duration` | uint256 | The timelock duration in seconds. | *** ### `abdicated` [#abdicated] ```solidity function abdicated(bytes4 selector) external view returns (bool) ``` Checks if the curator has abdicated the ability to execute a specific action. #### Parameters: [#parameters-51] | Name | Type | Description | | :--------- | :----- | :--------------------- | | `selector` | bytes4 | The function selector. | #### Return Values: [#return-values-45] | Name | Type | Description | | :---------- | :--- | :------------------------------------- | | `abdicated` | bool | Whether the action has been abdicated. | *** ### `executableAt` [#executableat] ```solidity function executableAt(bytes memory data) external view returns (uint256) ``` Returns the timestamp when a submitted timelocked action becomes executable. #### Parameters: [#parameters-52] | Name | Type | Description | | :----- | :---- | :---------------------------------- | | `data` | bytes | The ABI-encoded function call data. | #### Return Values: [#return-values-46] | Name | Type | Description | | :---------- | :------ | :------------------------------------------------ | | `timestamp` | uint256 | The timestamp when the action becomes executable. | *** ### `DOMAIN_SEPARATOR` [#domain_separator] ```solidity function DOMAIN_SEPARATOR() external view returns (bytes32) ``` Returns the EIP-712 domain separator used for permit signatures. #### Return Values: [#return-values-47] | Name | Type | Description | | :---------- | :------ | :-------------------- | | `separator` | bytes32 | The domain separator. | --- # Morpho Vaults Source: https://docs.morpho.org/developers/contracts/morpho-vaults ## Market [#market] Those following 3 elements are defined in the [Morpho related section](/get-started/resources/contracts/morpho#market-parameters). * `MarketParams` struct, * `market` struct, * `Id` of markets. Also we have: ```solidity struct MarketAllocation { MarketParams marketParams; uint256 assets; } ``` | Name | Type | Description | | :------------- | :--------------------------------------------------------------------------- | :-------------------------------- | | `marketParams` | [MarketParams](/get-started/resources/contracts/morpho/#marketparams-struct) | The market to allocate assets to. | | `assets` | uint256 | The amount of assets to allocate. | ## External Functions [#external-functions] ### deposit [#deposit] ```solidity function deposit(uint256 assets, address receiver) public override returns (uint256 shares) {} ``` Deposits `assets` of underlying token into the vault to mint vault shares to `receiver`. #### Parameters: [#parameters] | Name | Type | Description | | :--------- | :------ | :--------------------------------------------------- | | `assets` | uint256 | The quantity of asset to deposit. | | `receiver` | address | The address that will own the position on the vault. | #### Return Values: [#return-values] | Name | Type | Description | | :------- | :------ | :--------------------------------- | | `shares` | uint256 | The amount of vault shares minted. | ### mint [#mint] ```solidity function mint(uint256 shares, address receiver) public override returns (uint256 assets) {} ``` Mints exactly `shares` vault shares to receiver by depositing underlying tokens into the vault. #### Parameters: [#parameters-1] | Name | Type | Description | | :--------- | :------ | :--------------------------------------------------- | | `shares` | uint256 | The quantity of shares to mint. | | `receiver` | address | The address that will own the position on the vault. | #### Return Values: [#return-values-1] | Name | Type | Description | | :------- | :------ | :---------------------------------------- | | `assets` | uint256 | The amount of underlying token deposited. | ### withdraw [#withdraw] ```solidity function withdraw(uint256 assets, address receiver, address owner) public override returns (uint256 shares) {} ``` Withdraws `assets` of underlying token by burning vault shares of `owner`, and sends the withdrawn assets to `receiver`. `msg.sender` must be authorized to manage `owner`'s vault balance. #### Parameters: [#parameters-2] | Name | Type | Description | | :--------- | :------ | :-------------------------------------------------------------------- | | `assets` | uint256 | The quantity of asset to withdraw. | | `receiver` | address | The address that will receive the withdrawn assets. | | `owner` | address | The address that will see its position burnt of the amount of assets. | #### Return Values: [#return-values-2] | Name | Type | Description | | :------- | :------ | :--------------------------- | | `shares` | uint256 | The amount of shares burned. | ### redeem [#redeem] ```solidity function redeem(uint256 shares, address receiver, address owner) public override returns (uint256 assets) {} ``` Burns exactly `shares` vault shares from `owner` and sends the withdrawn assets of underlying tokens to `receiver`. `msg.sender` must be authorized to manage `owner`'s vault balance. #### Parameters: [#parameters-3] | Name | Type | Description | | :--------- | :------ | :-------------------------------------------------------------------- | | `shares` | uint256 | The quantity of shares to burn. | | `receiver` | address | The address that will receive the minted shares. | | `owner` | address | The address that will see its position burnt of the amount of shares. | #### Return Values: [#return-values-3] | Name | Type | Description | | :------- | :------ | :---------------------------------------- | | `assets` | uint256 | The amount of underlying token withdrawn. | ### acceptTimelock [#accepttimelock] ```solidity function acceptTimelock() external; ``` Accepts the pending timelock. This function is timelocked, which means `timelock` must have passed since the pending timelock submission for this function to be successfully called. ### acceptGuardian [#acceptguardian] ```solidity function acceptGuardian() external afterTimelock(pendingGuardian.validAt) {} ``` Accepts the pending guardian. This function is timelocked, which means `timelock` must have passed since the pending guardian submission for this function to be successfully called. ### acceptCap [#acceptcap] ```solidity function acceptCap(MarketParams memory marketParams) external; ``` Accepts the pending cap of the market defined by `marketParams`. This function is timelocked, which means `timelock` must have passed since the pending cap submission for this function to be successfully called. #### Parameters: [#parameters-4] | Name | Type | Description | | :--- | :------------------------------------------------------ | :-------------------------------------- | | `id` | [Id](/get-started/resources/contracts/morpho#market-id) | The id of the market to accept the cap. | ### skim [#skim] ```solidity function skim(address token) external {} ``` Skims the vault `token` balance to `skimRecipient`. #### Parameters: [#parameters-5] | Name | Type | Description | | :------ | :------ | :----------------------------------------------- | | `token` | address | The address of the token to skim the balance of. | The following functions are inheriting the [IERC4626 interface](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC4626.sol) As a reminder, there are specific types of [roles](/curate/concepts/roles/) in Morpho Vaults: 1. **Owner**, 2. **Curator**, 3. **Allocator**, 4. **Guardian**. ## OnlyOwner Functions [#onlyowner-functions] ### setName (V1.1) [#setname-v11] ```solidity function setName(string memory newName) external onlyOwner {} ``` Sets the vault's name to `newName`. #### Parameters: [#parameters-6] | Name | Type | Description | | :-------- | :----- | :------------------------ | | `newName` | string | The new name of the vault | ### setSymbol (V1.1) [#setsymbol-v11] ```solidity function setSymbol(string memory newSymbol) external onlyOwner {} ``` Sets the vault's symbol to `newSymbol`. #### Parameters: [#parameters-7] | Name | Type | Description | | :---------- | :----- | :-------------------------- | | `newSymbol` | string | The new symbol of the vault | ### setCurator [#setcurator] ```solidity function setCurator(address newCurator) external onlyOwner {}; ``` Sets `curator` to `newCurator`. #### Parameters: [#parameters-8] | Name | Type | Description | | :----------- | :------ | :------------------------------ | | `newCurator` | address | The address of the new curator. | ### setIsAllocator [#setisallocator] ```solidity function setIsAllocator(address newAllocator, bool newIsAllocator) external onlyOwner {}; ``` Sets `newAllocator` as an allocator or not (`newIsAllocator`). #### Parameters: [#parameters-9] | Name | Type | Description | | :--------------- | :------ | :-------------------------------------------------------------------------- | | `newAllocator` | address | The address of the new allocator. | | `newIsAllocator` | bool | A boolean (true or false) indicating if the address is an allocator or not. | ### setSkimRecipient [#setskimrecipient] ```solidity function setSkimRecipient(address newSkimRecipient) external onlyOwner {}; ``` Sets `skimRecipient` to `newSkimRecipient`. #### Parameters: [#parameters-10] | Name | Type | Description | | :----------------- | :------ | :------------------------------------------- | | `newSkimRecipient` | address | The address of the new recipient parameters. | ### submitTimelock [#submittimelock] ```solidity function submitTimelock(uint256 newTimelock) external onlyOwner {}; ``` Submits a `newTimelock`. * Warning: Reverts if a timelock is already pending. Revoke the pending timelock to overwrite it. * In case the new timelock is higher than the current one, the timelock is set immediately. #### Parameters: [#parameters-11] | Name | Type | Description | | :------------ | :------ | :------------------------------------------ | | `newTimelock` | uint256 | The value of the new timelock (in seconds). | ### setFee [#setfee] ```solidity function setFee(uint256 newFee) external onlyOwner {}; ``` Sets the `fee` to `newFee`. #### Parameters: [#parameters-12] | Name | Type | Description | | :------- | :------ | :-------------------------------------- | | `newFee` | uint256 | The value of the new fee scaled in wad. | ### setFeeRecipient [#setfeerecipient] ```solidity function setFeeRecipient(address newFeeRecipient) external onlyOwner {}; ``` Sets `feeRecipient` to `newFeeRecipient`. #### Parameters: [#parameters-13] | Name | Type | Description | | :---------------- | :------ | :------------------------------------ | | `newFeeRecipient` | address | The address of the new fee recipient. | Fees are minted as shares. To receive the underlying asset, the fee recipient must call `redeem()` at some point, either directly, or by granting allowance to another address that redeems on their behalf. **The fee recipient must be an address capable of interacting with the vault** (EOA, smart wallet, multisig, etc.). A fully passive address that never interacts with the chain won't be able to actually receive the fee in the underlying asset. ### submitGuardian [#submitguardian] ```solidity function submitGuardian(address newGuardian) external onlyOwner {}; ``` Submits a `newGuardian`. * Warning: a malicious guardian could disrupt the vault's operation, and would have the power to revoke any pending guardian. * In case there is no guardian, the gardian is set immediately. * Warning: Submitting a gardian will overwrite the current pending gardian. #### Parameters: [#parameters-14] | Name | Type | Description | | :------------ | :------ | :------------------------------- | | `newGuardian` | address | The address of the new guardian. | ## onlyCuratorRole Functions [#onlycuratorrole-functions] Note that owner and curator has the `onlyCuratorRole`. ### submitCap [#submitcap] ```solidity function submitCap(MarketParams memory marketParams, uint256 newSupplyCap) external onlyCuratorRole {}; ``` Submits a `newSupplyCap` for the market defined by `marketParams`. * Warning: Reverts if a cap is already pending. Revoke the pending cap to overwrite it. * Warning: Reverts if a market removal is pending. * In case the new cap is lower than the current one, the cap is set immediately. #### Parameters: [#parameters-15] | Name | Type | Description | | :------------- | :--------------------------------------------------------------------------- | :-------------------------------------- | | `marketParams` | [MarketParams](/get-started/resources/contracts/morpho/#marketparams-struct) | The market parameters. | | `newSupplyCap` | uint256 | The new supply cap in underlying units. | ### submitMarketRemoval [#submitmarketremoval] ```solidity function submitMarketRemoval(Id id) external onlyCuratorRole {}; ``` Submits a forced market removal from the vault, eventually losing all assets supplied to the market. * Funds can be recovered by enabling this market again and withdrawing from it (using `reallocate`), but assets will be distributed pro-rata to the shares at the time of withdrawal, not at the time of removal. * This forced removal is expected to be used as an emergency process in case a market constantly reverts. To softly remove a sane market, the curator role is expected to bundle a reallocation that empties the market first (using `reallocate`), followed by the removal of the market (using `updateWithdrawQueue`). * Warning: Removing a market with non-zero supply will instantly impact the vault's price per share. * Warning: Reverts for non-zero cap or if there is a pending cap. Successfully submitting a zero cap will prevent such reverts. #### Parameters: [#parameters-16] | Name | Type | Description | | :------------- | :--------------------------------------------------------------------------- | :--------------------- | | `marketParams` | [MarketParams](/get-started/resources/contracts/morpho/#marketparams-struct) | The market parameters. | ## onlyAllocatorRole Functions [#onlyallocatorrole-functions] Note that owner, curator and an address that has been set as an allocator has the `onlyAllocatorRole`. ### setSupplyQueue [#setsupplyqueue] ```solidity function setSupplyQueue(Id[] calldata newSupplyQueue) external onlyAllocatorRole {}; ``` Sets `supplyQueue` to `newSupplyQueue`. #### Parameters: [#parameters-17] | Name | Type | Description | | :--------------- | :----------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ | | `newSupplyQueue` | [Id](/get-started/resources/contracts/morpho#market-id) \[ ] | newSupplyQueue is an array of enabled markets, and can contain duplicate markets, but it would only increase the cost of depositing to the vault. | ### updateWithdrawQueue [#updatewithdrawqueue] ```solidity function updateWithdrawQueue(uint256[] calldata indexes) external onlyAllocatorRole {}; ``` Updates the withdraw queue. Some markets can be removed, but no market can be added. * Removing a market requires the vault to have 0 supply on it, or to have previously submitted a removal for this market (with the function `submitMarketRemoval`). * Warning: Anyone can supply on behalf of the vault so the call to `updateWithdrawQueue` that expects a market to be empty can be griefed by a front-run. To circumvent this, the allocator can simply bundle a reallocation that withdraws max from this market with a call to `updateWithdrawQueue`. * Warning: Removing a market with supply will decrease the fee accrued until one of the functions updating `lastTotalAssets` is triggered (deposit/mint/withdraw/redeem/setFee/setFeeRecipient). * Warning: `updateWithdrawQueue` is not idempotent. Submitting twice the same tx will change the queue twice. #### Parameters: [#parameters-18] | Name | Type | Description | | :-------- | :----------- | :-------------------------------------------------------------------------------------------- | | `indexes` | uint256 \[ ] | The indexes of each market in the previous withdraw queue, in the new withdraw queue's order. | ### reallocate [#reallocate] ```solidity function reallocate(MarketAllocation[] calldata allocations) external onlyAllocatorRole {}; ``` Reallocates the vault's liquidity so as to reach a given allocation of assets on each given market. * The allocator can withdraw from any market, even if it's not in the withdraw queue, as long as the loan token of the market is the same as the vault's asset. * The behavior of the reallocation can be altered by state changes, including: * Deposits on the vault that supplies to markets that are expected to be supplied to during reallocation. * Withdrawals from the vault that withdraws from markets that are expected to be withdrawn from during reallocation. * Donations to the vault on markets that are expected to be supplied to during reallocation. * Withdrawals from markets that are expected to be withdrawn from during reallocation. * Sender is expected to pass `assets = type(uint256).max` with the last MarketAllocation of `allocations` to supply all the remaining withdrawn liquidity, which would ensure that `totalWithdrawn` = `totalSupplied`. #### Parameters: [#parameters-19] | Name | Type | Description | | :------------ | :------------------------------------------------------------------------ | :------------------------------------------------ | | `allocations` | [MarketAllocation](/get-started/resources/contracts/morpho-vaults#market) | The respective allocations in each market chosen. | ## onlyGuardianRole Functions [#onlyguardianrole-functions] Note that owner and guardian has the `onlyAllocatorRole`. ### revokePendingTimelock [#revokependingtimelock] ```solidity function revokePendingTimelock() external onlyGuardianRole {}; ``` Revokes the pending timelock. * Does not revert if there is no pending timelock. ### revokePendingGuardian [#revokependingguardian] ```solidity function revokePendingGuardian() external onlyGuardianRole {}; ``` Revokes the pending guardian. ## onlyCuratorOrGuardianRole Functions [#onlycuratororguardianrole-functions] Note that owner, curator and guardian has the `onlyAllocatorRole`. ### revokePendingCap [#revokependingcap] ```solidity function revokePendingCap(Id id) external onlyCuratorOrGuardianRole {}; ``` Revokes the pending cap of the market defined by `id`. * Does not revert if there is no pending cap. #### Parameters: [#parameters-20] | Name | Type | Description | | :--- | :------------------------------------------------------ | :---------------------------------------------- | | `id` | [Id](/get-started/resources/contracts/morpho#market-id) | The id of the market to revoke the pending cap. | ### revokePendingMarketRemoval [#revokependingmarketremoval] ```solidity function revokePendingMarketRemoval(Id id) external onlyCuratorOrGuardianRole {}; ``` Revokes the pending removal of the market defined by `id`. * Does not revert if there is no pending market removal. #### Parameters: [#parameters-21] | Name | Type | Description | | :--- | :------------------------------------------------------ | :-------------------------------------------------- | | `id` | [Id](/get-started/resources/contracts/morpho#market-id) | The id of the market to revoke the pending removal. | # Errors Codes [#errors-codes] Below is a list of the different **Errors** that can be thrown when executing transactions on Morpho Vaults. **E.g**: One can try to execute an action on Morpho Vaults' contracts, and get: ```solidity reverted with an unrecognized custom error (return data: 0x46fedb57) ``` According to the following tables, this is an error that emitted the **AboveMaxTimelock()** function. | Custom Error | Error Signature (returned data) | | ----------------------------------------------- | ------------------------------- | | `AboveMaxTimelock()` | 0x46fedb57 | | `AlreadyPending()` | 0x49b204ce | | `AlreadySet()` | 0xa741a045 | | `AllCapsReached()` | 0xded0652d | | `BelowMinTimelock()` | 0x342b27be | | `DuplicateMarket(Id id)` | 0x074e4d4b | | `InconsistentAsset(Id id)` | 0xf17887ec | | `InconsistentReallocation()` | 0x9e36b890 | | `InvalidMarketRemovalNonZeroCap(Id id)` | 0xd2575d1a | | `InvalidMarketRemovalNonZeroSupply(Id id)` | 0x6316104f | | `InvalidMarketRemovalTimelockNotElapsed(Id id)` | 0x813435c5 | | `MarketNotCreated()` | 0x96e13529 | | `MarketNotEnabled(Id id)` | 0x11b0e0ab | | `MaxFeeExceeded()` | 0xf4df6ae5 | | `MaxQueueLengthExceeded()` | 0x80f2f7ae | | `NonZeroCap()` | 0xc48e3172 | | `NoPendingValue()` | 0xe5f408a5 | | `NotAllocatorRole()` | 0xf7137c0f | | `NotCuratorNorGuardianRole()` | 0xd080fa31 | | `NotCuratorRole()` | 0xca899cec | | `NotEnoughLiquidity()` | 0x4323a555 | | `NotGuardianRole()` | 0xf9f2fc9a | | `PendingCap(Id id)` | 0x42288ef1 | | `PendingRemoval()` | 0x4bec0146 | | `SupplyCapExceeded(Id id)` | 0xd018394f | | `TimelockNotElapsed()` | 0x6677a596 | | `UnauthorizedMarket(Id id)` | 0x9cd14834 | | `ZeroAddress()` | 0x867915ab | | `ZeroFeeRecipient()` | 0xcff9f194 | --- # Oracles Source: https://docs.morpho.org/developers/contracts/oracles ## Basics [#basics] Oracles are contracts that can be used as oracles for markets on Morpho. The oracles implement the IOracle interface defined in [IOracle.sol](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IOracle.sol). They return the price of 1 asset of collateral token quoted in 1 asset of loan token. ## price [#price] ```solidity function price() external view returns (uint256); ``` Returns the price of 1 asset of collateral token quoted in 1 asset of loan token, scaled by 1e36. * It corresponds to the price of 10\*\*(collateral token decimals) assets of collateral token quoted in 10 \*\*(loan token decimals) assets of loan token with `36 + loan token decimals - collateral token decimals` decimals of precision. ### Return values: [#return-values] | Name | Type | Description | | :------ | :------ | :---------------------------------------------------------------------------------------------- | | `price` | uint256 | The price rate of 1 asset of collateral token quoted in 1 asset of loan token (scaled by 1e36). | ## MorphoChainlinkOracleV2 [#morphochainlinkoraclev2] This is an oracle that uses Chainlink-interface-compliant feeds to provide price data. This oracle handles the following cases among others (let's say that our pair is A/B): * A/B is a feed (typically, stETH/ETH). * B/A is a feed (typically, ETH/USDC). * A/C and B/C are feeds (typically, stETH/ETH and USDC/ETH). * A/C, C/D and B/D are feeds (typically, WBTC/BTC, BTC/USD, USDC/USD). * A/D, and B/C, C/D are feeds (typically, USDC/USD, WBTC/BTC, BTC/USD). * A/C, C/D and B/E, E/D are feeds. * A/C and C/B are feeds (typically, WBTC/BTC and BTC/ETH). * A'/C and B/C are feeds, and there is an exchange rate between A and A'. (typically A=sDAI and A'=DAI). ## WstETH/stETH Exchange Rate Adapter [#wstethsteth-exchange-rate-adapter] A specific implementation, the `WstEthStEthExchangeRateChainlinkAdapter`, provides the exchange rate between wstETH and stETH as a Chainlink-interface-compliant feed. This adapter is deployed on Ethereum Mainnet at the address [0x905b7dAbCD3Ce6B792D874e303D336424Cdb1421](https://etherscan.io/address/0x905b7dabcd3ce6b792d874e303d336424cdb1421#code). ## MorphoChainlinkOracleV2Factory [#morphochainlinkoraclev2factory] This factory deploys `MorphoChainlinkOracleV2` instances. The factory facilitates the creation and indexing of MorphoChainlinkOracleV2 oracles. This factory contract streamlines the process of deploying new oracles, catering to various market pairs without the need to deploy individual contracts manually for each pair. ## Implementation [#implementation] > [Morpho Chainlink Oracle V2 Github repository](https://github.com/morpho-org/morpho-blue-oracles/tree/main/src/morpho-chainlink) The contract implements the [Oracle interface](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IOracle.sol) to fit Morpho specifications: ## Immutables [#immutables] * `BASE_VAULT`: baseVault. Pass address zero to omit this parameter. * `BASE_VAULT_CONVERSION_SAMPLE`: Base vault conversion sample. * `BASE_FEED_1`: First base feed. Pass address zero if the price = 1. * `BASE_FEED_2`: Second base feed. Pass address zero if the price = 1. * `BASE_TOKEN_DECIMALS`: Base token decimals. * `QUOTE_VAULT`: Quote vault. Pass address zero to omit this parameter. * `QUOTE_VAULT_CONVERSION_SAMPLE`: The sample amount of quote vault shares used to convert to underlying. Pass 1 if the quote asset is not a vault. Should be chosen such that converting `quoteVaultConversionSample` to assets has enough precision. * `QUOTE_FEED_1`: First quote feed. Pass address zero if the price = 1. * `QUOTE_FEED_2`: Second quote feed. Pass address zero if the price = 1. * `QUOTE_TOKEN_DECIMALS`: Quote token decimals. ## Assumptions [#assumptions] Here is the list of assumptions that guarantees the oracle behaves as expected: * The vaults, if set, are ERC4626-compliant. * The feeds, if set, are Chainlink-interface-compliant. * Decimals passed as argument are correct. * The base vaults's sample shares quoted as assets and the base feed prices don't overflow when multiplied. * The quote vault's sample shares quoted as assets and the quote feed prices don't overflow when multiplied. --- # Universal Rewards Distributors (Deprecated) Source: https://docs.morpho.org/developers/contracts/rewards Universal Rewards Distributors has been **deprecated**. ## Contract Overview [#contract-overview] The [`UniversalRewardsDistributor`](https://github.com/morpho-org/universal-rewards-distributor/tree/main) contract facilitates the distribution of various reward tokens to multiple accounts using different Merkle trees. It is designed to operate in a permissionless manner and is inspired by Morpho's current rewards distributor. The contract enables the dynamic update of distribution parameters, management of reward claims, and ensures the integrity of the distribution through the use of Merkle proofs. ## Functions [#functions] ### submitRoot [#submitroot] ```solidity function submitRoot(bytes32 newRoot, bytes32 newIpfsHash) external onlyUpdaterRole; ``` Submits a new merkle root for the distribution of rewards. * Can only be called by an address with updater role. * Emits a `PendingRootSet` event. #### Parameters: [#parameters] | Name | Type | Description | | :------------ | :------ | :--------------------------------------------------------- | | `newRoot` | bytes32 | The new merkle root. | | `newIpfsHash` | bytes32 | The optional ipfs hash containing metadata about the root. | ### acceptRoot [#acceptroot] ```solidity function acceptRoot() external; ``` Accepts and sets the current pending merkle root after the timelock has expired. * Can be called by any address. * Reverts if no pending root is set or if the timelock has not expired. * Emits a `RootSet` event upon successful execution. ### revokePendingRoot [#revokependingroot] ```solidity function revokePendingRoot() external onlyUpdaterRole; ``` Revokes the current pending merkle root. * Can only be called by an address with updater role. * Reverts if no pending root is set. * Emits a `PendingRootRevoked` event. ### claim [#claim] ```solidity function claim(address account, address reward, uint256 claimable, bytes32[] calldata proof) external returns (uint256 amount); ``` Claims rewards for a specific account. * Reverts if the root is not set, if the proof is invalid, or if the claimable amount is less than the amount already claimed. * Emits a `Claimed` event upon successful execution. #### Parameters: [#parameters-1] | Name | Type | Description | | :---------- | :--------- | :--------------------------------------------- | | `account` | address | The address to claim rewards for. | | `reward` | address | The address of the reward token. | | `claimable` | uint256 | The overall claimable amount of token rewards. | | `proof` | bytes32\[] | The merkle proof that validates this claim. | #### Return values: [#return-values] | Name | Type | Description | | :------- | :------ | :---------------------------------- | | `amount` | uint256 | The amount of reward token claimed. | ### setRoot [#setroot] ```solidity function setRoot(bytes32 newRoot, bytes32 newIpfsHash) external onlyUpdaterRole; ``` Forces update of the distribution's merkle root, bypassing the timelock. * Can only be called by the owner of the distribution or by updaters if there is no timelock. * Reverts if the new root is the same as the current root and if the caller is unauthorized to change the root. * Emits a `RootSet` event upon successful execution. #### Parameters: [#parameters-2] | Name | Type | Description | | :------------ | :------ | :--------------------------------------------------------- | | `newRoot` | bytes32 | The new merkle root. | | `newIpfsHash` | bytes32 | The optional ipfs hash containing metadata about the root. | ### setTimelock [#settimelock] ```solidity function setTimelock(uint256 newTimelock) external onlyOwner; ``` Sets a new timelock for the distribution. * Can only be called by the owner of the distribution. * Reverts if the new timelock is the same as the current timelock. * Emits a `TimelockSet` event upon successful execution. #### Parameters: [#parameters-3] | Name | Type | Description | | :------------ | :------ | :---------------- | | `newTimelock` | uint256 | The new timelock. | ### setRootUpdater [#setrootupdater] ```solidity function setRootUpdater(address updater, bool active) external onlyOwner; ``` Sets or unsets an address as a root updater. #### Parameters: [#parameters-4] | Name | Type | Description | | :-------- | :------ | :------------------------------------------------ | | `updater` | address | The address of the root updater. | | `active` | bool | Whether the root updater should be active or not. | * Can only be called by the owner of the distribution. * Reverts if the updater status is already set as specified. * Emits a `RootUpdaterSet` event upon successful execution. ### setOwner [#setowner] ```solidity function setOwner(address newOwner) external onlyOwner; ``` Transfers ownership of the distribution to a new address. * Can only be called by the current owner. * Reverts if the new owner is the same as the current owner. * Emits an `OwnerSet` event upon successful execution. #### Parameters: [#parameters-5] | Name | Type | Description | | :--------- | :------ | :---------------------------- | | `newOwner` | address | The address of the new owner. | ## Events [#events] The contract emits events to signify important actions and state changes. Here are the events associated with the `UniversalRewardsDistributor` contract: * `PendingRootSet`: Emitted when a new pending root is submitted. * `PendingRootRevoked`: Emitted when a pending root is revoked. * `RootSet`: Emitted when a new root is accepted and set. * `Claimed`: Emitted when rewards are successfully claimed. * `RootUpdaterSet`: Emitted when a root updater's status is changed. * `OwnerSet`: Emitted when a new owner is set for the distribution. * `TimelockSet`: Emitted when the timelock is updated. ## Roles [#roles] There are only 2 roles: * **Owner**: owner of the contract that has access to the updater's functions and [`setTimelock`](#settimelock), [`setRootUpdater`](#setrootupdater), and [`setOwner`](#setowner) functions. * **Updater**: addresses that can trigger the [`submitRoot`](#submitroot), [`setRoot`](#setroot), and [`revokePendingRoot`](#revokependingroot) functions. --- # Morpho Vaults for Earn products Source: https://docs.morpho.org/developers/earn/get-started Integrating Morpho Vaults (Earn) into your application means implementing the infrastructure that allows users to deposit assets into vaults built on top of Morpho's lending protocol. For new Earn integrations on Morpho, [Morpho Vault V2](/learn/concepts/vault-v2/) is the recommended vault surface. Users may receive variable yield from borrower-paid interest on their deposited assets, alongside incentives (rewards) from different rewards providers. ## Key Components of Earn Integration [#key-components-of-earn-integration] When building earn products with Morpho Vaults, you need to integrate three main components: 1. **Vault Operations**: Deposit, withdraw, and position management 2. **Yield Tracking**: Vault APY calculation and performance monitoring 3. **Rewards Integration**: Discovery, tracking, and claiming of additional incentives Here is a quick visual for better understanding: {" "}
Integrating Earn gives users access to: 1. **Configurable Risk Parameters**: Unlike traditional lending platforms where users are exposed to all assets in the pool, Morpho Vaults allow selective exposure to specific collateral types based on risk preferences. 2. **Permissionless Infrastructure**: Any entity (individual, DAO, protocol) can create and manage vaults with different risk parameters and allocation strategies. 3. **Non-Custodial Architecture**: All positions remain fully controlled by the user with transparent onchain verification of allocations and immutable vault logic. ## Technical Components [#technical-components] From a technical standpoint, integrating Earn involves: 1. **Vault Discovery and Display**: Implementing systems to discover available vaults, display their key metrics (APY, TVL, allocation breakdown), and present risk profiles to users. 2. **Transaction Handling**: Building the interface and backend support for deposit and withdrawal operations, including: * Asset approval workflows * Transaction status monitoring * Receipt confirmation and position updates * Rewards tracking updates 3. **Position Management**: Creating interfaces for users to: * View their current vault positions * Track historical performance * Monitor yield accrual * Perform deposits & withdrawals * Claim rewards 4. **Risk Transparency**: Building displays that clearly communicate: * Which Morpho markets the vault allocates to * Collateral types users are exposed to * Current utilization ratios * Current allocation strategy and any pending changes ## Rewards Considerations [#rewards-considerations] A complete Earn integration should include rewards functionality as one of the core components. Many Morpho Vaults earn additional incentives beyond base lending yield through reward campaigns. Key aspects to integrate: * **Rewards Discovery**: Identify available reward programs for vault depositors * **Rewards Tracking**: Display accruing and claimable rewards in real-time * **Claiming Process**: Implement claim functionality using Merkle-proof based distribution via Merkl * **Multi-Token Support**: Handle various reward tokens from different reward providers * **Program Awareness**: Display active campaigns and their respective parameters For detailed implementation guidance, see the [Rewards integration guide](/developers/rewards/get-started). ## Assets Flow - Earn [#assets-flow---earn] Understanding the complete flow of assets is crucial for proper integration: ### 1. Deposit [#1-deposit] * User deposits an asset (e.g., USDC, ETH) into a Morpho Vault * User receives receipt tokens (ERC4626 tokens) representing their proportional ownership * The vault allocates the deposited assets across: * Approved adapters configured by the curator's strategy * Today, the most common underlying yield source is Morpho market exposure routed through Morpho Vault V2 adapters * These markets are permissionless pools containing a loan token (what was deposited) and a collateral token ### 2. Yield Generation [#2-yield-generation] * Borrowers deposit collateral into Morpho Markets and borrow the loan token * Borrowers pay interest on borrowed amounts * This interest accrues to suppliers (the vault, in this case) * The vault's receipt token price increases as interest accumulates, representing yield for depositors ### 3. Withdrawal Process [#3-withdrawal-process] * Users can redeem their vault receipt tokens for the underlying asset at any time * Vault V2 first uses idle assets, then its configured liquidity path and underlying adapters as needed to source liquidity * Users receive their original deposit plus accrued yield (minus any performance or management fee) ## Functional Integration Requirements [#functional-integration-requirements] For a complete Earn integration, your system needs to implement: 1. **Read Operations**: * Query vault metadata (name, symbol, asset, curator, fees) * Fetch current APY and historical performance * Display current allocation across markets * Show user-specific position data * Present risk parameters of underlying markets 2. **Write Operations**: * Handle asset approvals * Execute deposits & withdrawals * Facilitate claiming any associated rewards 3. **Monitoring Capabilities**: * Track position changes * Alert users to significant vault parameter changes * Update yield information in real-time ## Risk Considerations [#risk-considerations] A complete Earn integration should help users understand: 1. **Smart Contract Risk**: Exposure to both Morpho and Vault contracts 2. **Market Risk**: Exposure to specific collateral types and their volatility 3. **Curator Risk**: Dependence on curator's technical configuration and ongoing parameter updates 4. **Liquidity Risk**: Potential for withdrawal delays during high utilization 5. **Oracle Risk**: Vulnerability to incorrect price feeds affecting underlying markets By properly integrating these components, your application can offer users access to Morpho's capital-efficient lending infrastructure through a curated, simplified experience that abstracts away much of the complexity while maintaining transparency about underlying risks and operations. --- # Data Dashboards Source: https://docs.morpho.org/developers/ecosystem/data-dashboards [Dune](https://dune.com/home) defines itself as the Crypto's Data Hub. It is a platform that provides comprehensive data and analytics for the cryptocurrency space. It offers access to community dashboards, allowing users to track metrics for DeFi, NFTs, and various crypto projects. It provides data infrastructure to Web3 teams, supporting multiple blockchains and offering tools for data analysis, integration, and application development. Multiple open source dashboards are provided below: ## High-level dashboards [#high-level-dashboards] * [GMorpho Dashboard](https://dune.com/morpho/gmorpho-dashboard) * [Morpho protocol](https://dune.com/morpho/multichain-activity) ## Domain specific dashboards [#domain-specific-dashboards] * [Morpho Migrations](https://dune.com/morpho/morpho-migration) * [Coinbase Onchain Borrowing & Lending](https://dune.com/morpho/coinbase-onchain-lending-borrowing) * [Vault Performance](https://dune.com/morpho/single-vault-performance) * [Vault Curators](https://dune.com/morpho/vaults-curators-analysis) * [Morpho Credit Risk](https://dune.com/morpho/morpho-credit-risk) * [Morpho Liquidations](https://dune.com/morpho/morpho-liquidation) If you want to be displayed here, please reach out via the chatbox on **[help.morpho.org](https://help.morpho.org)**. --- # Liquidation Bots Source: https://docs.morpho.org/developers/ecosystem/liquidation-bots Liquidation bot implementations built by Morpho and the community for monitoring markets and liquidating unhealthy positions. New to Morpho liquidations? Start with the [Liquidation concept section](/learn/concepts/liquidation/). The content provided in this document is contributed by the community and is shared as examples. Morpho Association nor author of the repository cannot be held responsible for any losses or damages that may result from the use of this information. Users are advised to conduct their own research and exercise caution when applying any strategies or methods described herein. ## Maintained by Morpho [#maintained-by-morpho] ### Morpho Blue liquidation bot [#morpho-blue-liquidation-bot] Typescript & Solidity · by [morpho-labs](https://github.com/morpho-labs/) A simple, fast, and easily deployable liquidation bot for the Morpho protocol. This bot is entirely based on RPC calls and is designed to be easy to configure, customizable, and ready to deploy on any EVM-compatible chain. ### Educational liquidation bot [#educational-liquidation-bot] Typescript & Solidity · by [morpho-labs](https://github.com/morpho-labs/) This contribution presents a simple implementation of a liquidation bot that can liquidate unhealthy position on Morpho. Using Typescript and Solidity. ## Community implementations [#community-implementations] ### Rust liquidator [#rust-liquidator] Rust & Solidity · by [etherhood](https://github.com/etherhood/) This contribution presents a detailed strategy for liquidations using Rust and Solidity. ### Go liquidator [#go-liquidator] Go & Solidity · by [zach030](https://github.com/zach030/) This contribution presents a detailed strategy for liquidations using Go and Solidity. ## Get listed [#get-listed] If you want to be displayed here, please reach out via the chatbox on **[help.morpho.org](https://help.morpho.org)**. --- # Community Oracles Source: https://docs.morpho.org/developers/ecosystem/oracles Solidity oracle adapters and tools built by the community for use with Morpho markets. Most expose a Chainlink-compatible interface, for use as base or quote feeds with `MorphoChainlinkOracleV2Factory`. New to Morpho oracles? Start with the [Oracle concept section](/learn/concepts/oracle/). The content provided in this document is contributed by the community and is shared as examples. Morpho Association nor author of the repository cannot be held responsible for any losses or damages that may result from the use of this information. Users are advised to conduct their own research and exercise caution when applying any strategies or methods described herein. ## At a glance [#at-a-glance] | Integration | Prices | Made by | | ------------------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | [Chainlink](#chainlink) | Oracle evaluation framework | [Chainlink oracle documentation](https://docs.google.com/document/d/1bDVGzmTB0Sg22Jk6JzDliRZDvqcNZKLXoC-y5OloXdQ/edit?tab=t.4e7gxqq73srw) | | [Pyth](#pyth) | Any Pyth price feed | [aditya520](https://github.com/aditya520) | | [Pendle PT](#pendle-pt) | Pendle Principal Tokens | [Horace](https://github.com/horacepan) | | [LST/LRT exchange rates](#lstlrt-exchange-rates) | ezETH, rsETH, swETH, rswETH, weETH | [frontier159](https://github.com/frontier159) | | [sfrxETH exchange rate](#sfrxeth-exchange-rate) | sfrxETH/frxETH | [Horace](https://github.com/horacepan) | | [f(x) Protocol](#fx-protocol) | fxUSD, rUSD, btcUSD, fETH, xETH, and more | [chiaki644](https://github.com/chiaki644) | | [LlamaRisk crvUSD LP](#llamarisk-crvusd-lp) | Tri-pool LP crvUSD | [LlamaRisk](https://github.com/llama-risk) | | [Origami](#origami) | Origami Finance assets | [TempleDAO](https://github.com/TempleDAO) | | [Wildcat Market](#wildcat-market) | ERC-4626 wrapped Wildcat market tokens | [Wintermute Research](https://github.com/WintermuteResearch) | | [Oval](#oval) | Wraps any Chainlink feed (OEV recapture) | [Oval](https://github.com/UMAprotocol) | | [Ojo Yield Risk Engine](#ojo-yield-risk-engine) | Caps yield-asset appreciation | [Ojo](https://github.com/ojo-network) | ## Oracle networks [#oracle-networks] ### Chainlink [#chainlink] by Chainlink Labs Morpho Oracle evaluation framework documentation by [Chainlink](https://docs.google.com/document/d/1bDVGzmTB0Sg22Jk6JzDliRZDvqcNZKLXoC-y5OloXdQ/edit?tab=t.4e7gxqq73srw). ### Pyth [#pyth] by [aditya520](https://github.com/aditya520) Morpho V1 oracle implementation backed by [Pyth](https://pyth.network) price feeds, created by the Pyth Data Association. Use it to build dedicated market oracles from any Pyth feed. ## Yield-bearing & staked assets [#yield-bearing--staked-assets] ### Pendle PT [#pendle-pt] by [Horace](https://github.com/horacepan) Wraps Pendle's PT oracle in Chainlink's `AggregatorV3` interface, plus a factory for deploying the adapter and Pendle's `SparkLinearDiscountOracle`. Usable as base or quote feeds with `MorphoChainlinkOracleV2Factory`. ### LST/LRT exchange rates [#lstlrt-exchange-rates] by [frontier159](https://github.com/frontier159) Exchange-rate adapters against ETH for ezETH (Renzo), rsETH (Kelp), swETH and rswETH (Swell), and weETH (ether.fi). ### sfrxETH exchange rate [#sfrxeth-exchange-rate] by [Horace](https://github.com/horacepan) sfrxETH/frxETH exchange-rate adapter following the wstETH-stETH pattern, using `pricePerShare` instead of `getPooledEthByShares`. ## Protocol-specific adapters [#protocol-specific-adapters] ### f(x) Protocol [#fx-protocol] by [chiaki644](https://github.com/chiaki644) Supports all f(x) protocol tokens: fxUSD, rUSD, btcUSD, fETH, xETH, xstETH, xfrxETH, xezETH, xeETH, xWBTC. Token addresses in the [f(x) deployments list](https://github.com/AladdinDAO/deployments/blob/main/deployments.mainnet.md). ### LlamaRisk crvUSD LP [#llamarisk-crvusd-lp] by [LlamaRisk](https://github.com/llama-risk) Oracle for tri-pool LP crvUSD, with an accompanying [research write-up](https://www.llamarisk.com/research/morpho-crvusd-vault). ### Origami [#origami] by [TempleDAO](https://github.com/TempleDAO) Chainlink-interface-compliant feeds by TempleDAO for Origami Finance assets. ### Wildcat Market [#wildcat-market] by [Wintermute Research](https://github.com/WintermuteResearch) Prices ERC-4626 wrapped Wildcat market tokens. Deployers can define derisking parameters as a function of delinquency length, based on expected creditworthiness. ## Risk & MEV tools [#risk--mev-tools] ### Oval [#oval] by [Oval](https://github.com/UMAprotocol) OEV recapture tool by UMA. Wraps existing oracle feeds and outputs Chainlink-compliant prices; deployable via factory for Morpho markets ([tutorial](https://docs.oval.xyz/integration/morpho-deployment)). ### Ojo Yield Risk Engine [#ojo-yield-risk-engine] by [Ojo](https://github.com/ojo-network) Puts a static cap on how much a yield asset can appreciate over time, mitigating ERC-4626 donation/inflation attacks where users artificially increase a vault token's value in order to borrow more against it. ## Get listed [#get-listed] Built an oracle adapter for Morpho? Reach out via the chatbox on **[help.morpho.org](https://help.morpho.org)**. --- # Fixed Rate Markets (Midnight) Source: https://docs.morpho.org/developers/midnight/get-started Integrating Morpho Midnight into your application means implementing the infrastructure that allows users to lend at fixed rates and borrow at fixed rates across isolated, fixed-term markets. Unlike variable rate lending where rates fluctuate every block, Midnight rates are locked at the moment of the trade. Each unit represents a claim on (or obligation to repay) exactly one loan token at maturity. The rate emerges from the price at which units are traded, not from an interest rate model. ## Key Integration Components [#key-integration-components] When building fixed-rate earn and borrow products with Morpho Midnight, you need to integrate four main components: 1. **Market discovery and selection**: Let users browse isolated fixed-term markets by chain, loan token, maturity, accepted collaterals, and available bid/ask liquidity. 2. **Quote and execution flows**: Fetch takeable offers, apply price/slippage protections, and execute offers. 3. **Position and risk tracking**: Show users their credit, debt, collateral, maturity, pending fees, liquidation risk, and transaction history. Integrating Midnight gives users access to: 1. **Fixed-rate lending and borrowing**: Rates are locked at execution because users trade units at a specific price. Lenders know the return they are targeting, and borrowers know the repayment obligation they create. 2. **Isolated fixed-term markets**: Each market is defined by one loan token, one maturity, and a fixed accepted-collateral set. Market parameters are immutable after creation, which makes risk easier to reason about. 3. **Fungible credit and debt units**: Positions are denominated in units. Credit units represent claims on loan tokens at maturity; debt units represent obligations to repay loan tokens. 4. **Secondary liquidity**: Lenders can sell credit units before maturity, and borrowers can buy units to close debt before maturity, depending on available market liquidity. ## Assets Flow - Lend [#assets-flow---lend] Understanding the lending flow is essential for a clean Earn integration: 1. **Browse markets**: The user selects a market by chain, loan token, maturity, accepted collateral set, among many other parameters of interest. 2. **Estimate return**: The app converts ask prices for the selected market into implied fixed rates. 3. **Get a quote**: The Router returns executable takeable offers for the requested lend size and slippage, including fallback liquidity in case offers are consumed before execution. 4. **Buy credit units**: The user takes asks and spends loan tokens to buy credit units. Each credit unit is a claim on one loan token at maturity. 5. **Track position**: The user’s position shows credit, pending fees, cost basis, effective rate, and any loss factor. 6. **Exit or redeem**: Before maturity, the user may sell credit units through secondary liquidity. At or after maturity, credit can be redeemed 1:1, net of fees and losses. ## Assets Flow - Borrow [#assets-flow---borrow] For borrowing integrations, the flow is symmetric but risk and collateral management are central: 1. **Browse markets**: The user selects a market by chain, loan token, maturity, accepted collateral set, among many other parameters of interest. 2. **Estimate cost**: The app converts bid prices into implied fixed borrow rates. The highest bid is the best available price for the borrower. 3. **Supply collateral**: The borrower deposits accepted collateral. Borrowing capacity depends on the value of each collateral multiplied by its LLTV. 4. **Fetch a quote**: The Router returns executable takeable offers against bids for the requested borrow size. 5. **Sell units**: The borrower takes bids, sells debt units, and receives loan tokens. The debt amount is the number of units owed at maturity. 6. **Monitor health**: The position remains healthy while `maxDebt ≥ debt`. If debt exceeds maxDebt before maturity, the position can be partially liquidated. After maturity, unpaid debt can be partially or fully liquidated. 7. **Repay or exit**: The borrower can repay at maturity or buy units on the secondary market before maturity to close debt early. ## Functional Integration Requirements [#functional-integration-requirements] For a complete Midnight integration, your system needs to implement: 1. **Read Operations**: * Query market definitions and live market state. * Fetch books, bids, asks, quotes, and takeable offers. * Display user positions, transactions, collateral balances, credit, debt, pending fees, and effective rates. * Show fees and maturity status. 2. **Write Operations**: * Handle token approvals, permits, and Midnight Bundles authorization. * Execute lend, borrow, repay, collateral supply, collateral withdrawal, and secondary exit flows. * For makers, publish signed offers and validate offer payloads before broadcasting. 3. **Monitoring Capabilities**: * Track position changes, offer consumption, transaction status, market liquidity, and liquidation events. * Alert users to maturity, deteriorating collateral health, failed quotes, exhausted liquidity, or rejected offer payloads. ## Risk Considerations [#risk-considerations] A complete Midnight integration should help users understand: 1. **Market isolation risk**: Each market has its own maturity, loan token, and fixed collateral set. 2. **Collateral risk**: Lenders are exposed to the full accepted collateral set of a market, not only the collateral currently posted by borrowers. 3. **Liquidation risk**: Borrowers can be liquidated if debt exceeds maxDebt before maturity, and unpaid debt can be fully liquidated after maturity. 4. **Liquidity risk**: Secondary exits and quote execution depend on available offers. Offers can be consumed before a transaction lands. 5. **Oracle risk**: Collateral health depends on oracle prices used to value collateral. 6. **Fee risk**: Settlement fees affect taker execution prices, while continuous fees reduce lender credit over time. 7. **Bad debt risk**: If liquidation does not fully cover debt, losses can be socialized to lenders through the market loss factor. ## UX Requirements [#ux-requirements] ### What your interface should surface [#what-your-interface-should-surface] 1. **Show both price and rate**: Users trade units at a price, while the app usually presents the result as an implied fixed rate. 2. **Distinguish asks and bids**: Lenders take asks; borrowers take bids. Make this clear in the UI. 3. **Explain maturity**: Users should understand what happens before maturity, at maturity, and after maturity. 4. **Show execution protection**: Display slippage, worst acceptable price, minimum units for lenders, or maximum units for borrowers. 5. **Surface collateral exposure**: For lend products, show the market accepted collateral set. For borrow products, show collateral requirements. 6. **Attribute Morpho**: Include a clear “Powered by Morpho” mention and appropriate protocol disclaimers in the user flow. ### Brand & attribution [#brand--attribution] * Surface “Powered by Morpho” on the screens where users actually interact with Morpho functionality. Integrators who get this framing right tend to have cleaner regulatory conversations and clearer user support. * The official `powered-by-morpho` web component or the official static badge both work. If either does not fit a surface, contact the support team so the asset pack can be adapted rather than rebuilding the mark. * Matching the badge theme to the UI surface (light on light, dark on dark) tends to read best. * Somewhere reachable from the badge, users benefit from seeing the short disclaimer. The web component does this via tooltip. **Reference.** [https://brand.morpho.org](https://brand.morpho.org) / [snippet.js](https://brand.morpho.org/snippet.js) --- # Integrating Morpho Rewards Source: https://docs.morpho.org/developers/rewards/get-started Morpho's rewards system allows protocols, DAOs, and token issuers to incentivize specific behaviors on the Morpho protocol—such as supplying assets to vaults, supplying or borrowing loan assets in markets, or depositing collateral in markets. As an integrator, your role is to help users discover, track, and claim these rewards through your application. ## Understanding the Rewards Ecosystem [#understanding-the-rewards-ecosystem] Rewards on Morpho are distributed through **Merkl**. **Migration Complete** The rewards distribution has been fully migrated from the Morpho rewards stack (URD) to the Merkl stack following [MIP 111](https://snapshot.box/#/s\:morpho.eth/proposal/0xadf3862d97cf4e8bb84a4b3d5117abe1a9db774e92ad97c65bde22214c0b76c9). All new MORPHO rewards and third-party rewards are distributed via Merkl. For unclaimed historical rewards, users can visit [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). **Important: Non-Claimable Addresses** If you're interacting with Morpho through a smart contract or address that cannot claim rewards, please contact Merkl before depositing to have your rewards redirected. Rewards allocated to addresses that cannot claim aren't recoverable or redirectable after the fact and will stay permanently unclaimable. ## Merkl: The Current Standard [#merkl-the-current-standard] [Merkl](https://merkl.xyz/) is a third-party rewards distribution platform that now handles Morpho ecosystem rewards. It offers: * **Flexible distribution**: Rewards updated every 8 hours * **Multi-protocol support**: Used across DeFi, not just Morpho * **Automated calculations**: Offchain computation of eligible rewards * **Standardized claiming**: Consistent API and claiming process **When to use Merkl:** * All new reward programs (July 2025 onwards) * Third-party incentive programs * Current MORPHO token distributions ## Legacy Rewards (Pre-Merkl) [#legacy-rewards-pre-merkl] The Universal Rewards Distributor (URD) was Morpho's previous rewards system. The `rewards.morpho.org` API has been deprecated. Users with unclaimed historical rewards can look them up and claim via [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). ## Who Should Integrate Rewards? [#who-should-integrate-rewards] You should integrate rewards if you're building: * **Earn Products**: Display additional APR from rewards alongside base vault yield * **Borrow Products**: Show borrowers incentives for taking loans * **Portfolio Dashboards**: Provide users with a complete view of their Morpho earnings * **Aggregators**: Compare total yields (base + rewards) across protocols * **Reward Platforms**: Create specialized UIs for rewards discovery and claiming ## What You'll Need to Integrate [#what-youll-need-to-integrate] A complete rewards integration involves three main components: ### 1. Rewards Discovery & Display [#1-rewards-discovery--display] Fetch and display available rewards for vaults and markets: * Query reward rates from Merkl API or Morpho API * Calculate total APY (base yield + reward APR) * Show reward token types and amounts * Display program timelines and eligibility ### 2. User Balance Tracking [#2-user-balance-tracking] Show users their accrued and claimable rewards: * Fetch user-specific reward balances * Display rewards by token and program * Calculate USD values of rewards * Update balances in real-time ### 3. Claiming Interface [#3-claiming-interface] Enable users to claim their rewards: * Support Merkl claim flow * Handle transaction signing and confirmation * Provide claim status feedback ## Integration Paths [#integration-paths] First, [Understand Reward Programs](/developers/rewards/concepts/reward-campaigns) and learn the different program types. Choose your integration approach based on your needs: ### Quick Start (Display Only) [#quick-start-display-only] Just want to show rewards APR alongside base yield? Start here: 1. [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) - Learn to query both APIs 2. [Integrate Display](/developers/rewards/tutorials/integrate-display) - Show rewards in your UI ### Full Integration (Display + Claiming) [#full-integration-display--claiming] Building a complete product? Follow this path: 1. [Merkl Distribution System](/developers/rewards/concepts/distribution-system) - Understand Distribution 2. [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) - Query rewards information 3. [Claim Rewards](/developers/rewards/tutorials/claim-rewards) - Implement claiming via Merkl 4. [Complete Integration Guide](/developers/rewards/guides/complete-integration) - See a full working example ### Creating Rewards (Issuers) [#creating-rewards-issuers] Are you a protocol or DAO looking to create a rewards program? 1. [Create a Program](/developers/rewards/tutorials/create-program) - Step-by-step program creation ## Need Help? [#need-help] * **Technical Support**: Visit [help.morpho.org](https://help.morpho.org) * **Merkl Support**: Reach out via [Merkl's documentation](https://docs.merkl.xyz/) --- # Morpho SDKs Source: https://docs.morpho.org/developers/sdks/get-started The Morpho Software Development Kits (SDKs) are a suite of TypeScript packages for building on the Morpho protocol - from a full-featured DeFi application to a liquidation bot, an indexer, or a simple data dashboard. The entrypoint is the **[Morpho SDK](/developers/sdks/morpho-sdk/)** - the default abstraction layer that produces ready-to-send transactions for **[VaultV2](/developers/sdks/morpho-sdk/vault/)**, **[Variable Rate - Blue Market](/developers/sdks/morpho-sdk/blue/)** (Morpho Blue), and **[Fixed Rate - Midnight Market](/developers/sdks/morpho-sdk/midnight/)** in a single, consistent surface. When you need raw entity classes, low-level fetchers, or custom calldata composition, the Morpho SDK ships those primitives as subpath exports and dependencies, so you never have to reach past it. **New here?** Go straight to the [Morpho SDK](/developers/sdks/morpho-sdk/) - it's what powers Morpho's own consumer surfaces and is the SDK you almost certainly want. ## The Morpho SDK [#the-morpho-sdk] [**`@morpho-org/morpho-sdk`**](/developers/sdks/morpho-sdk/) - the **default and recommended SDK** for application integrators. It is the abstraction layer that simplifies the Morpho protocol: * One consistent abstraction layer across **[VaultV2](/developers/sdks/morpho-sdk/vault/)**, **[Variable Rate - Blue Market](/developers/sdks/morpho-sdk/blue/)** (Morpho Blue), and **[Fixed Rate - Midnight Market](/developers/sdks/morpho-sdk/midnight/)**, with VaultV1 (MetaMorpho) supported under the hood for migrations / liquidity reallocation * Builds final, deep-frozen `viem` `Transaction` objects - no calldata wrangling * Handles ERC-20 approvals, **Permit / Permit2** signatures, and **Morpho `setAuthorization`** for GeneralAdapter1 via a unified `getRequirements()` flow * Composes **Bundler3 / GeneralAdapter1** bundles for you (atomic supply+borrow, repay+withdraw, Blue-to-Blue refinance, V1→V2 migration, native-token wrapping) * Slippage-protected by default (`maxSharePrice` / `minSharePrice`, LLTV buffer) * Optional **shared-liquidity reallocations** through the PublicAllocator for borrow, withdraw, supply+borrow, and refinance flows — see the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/) * **Midnight fixed-rate flows** - take offers, publish maker offers, and maintain positions on Morpho's fixed-rate, fixed-term orderbook * Strict TypeScript, no `any`, dedicated error classes for every failure mode ```bash pnpm add @morpho-org/morpho-sdk viem ``` → [Read the Morpho SDK guide](/developers/sdks/morpho-sdk/) · [Source on GitHub](https://github.com/morpho-org/sdks/tree/main/packages/morpho-sdk) · [Package on npm](https://www.npmjs.com/package/@morpho-org/morpho-sdk) ## Fixed rate (Midnight) [#fixed-rate-midnight] Midnight - fixed-rate, fixed-term lending through signed maker offers matched in an onchain orderbook - is a surface **of the Morpho SDK**, not a separate SDK: the [Midnight page](/developers/sdks/morpho-sdk/midnight/) covers taker, maker, and position-maintenance transactions through the same `getRequirements` / `buildTx` flow. Underneath, the Morpho SDK ships the low-level Midnight toolkit (the formerly standalone Midnight SDK, now folded in) and re-exports most of its surface on its own subpaths - `/midnight-api`, `/abis`, `/constants`, `/utils`, and `/errors`. Reach for the toolkit directly when you need offer construction, ratification, books and quotes, or tick math: → [Read the Midnight toolkit guide](/developers/sdks/morpho-sdk/midnight/) · [Source on GitHub](https://github.com/morpho-org/sdks/tree/main/packages/midnight-sdk) ## Reaching below the abstraction layer [#reaching-below-the-abstraction-layer] When you need to reach below the abstraction layer - for raw entity classes, low-level on-chain fetchers, custom calldata composition, or simulation pipelines - the Morpho SDK ships those primitives as subpath exports, so you never need a separate package: * **`@morpho-org/morpho-sdk/entities`** - Core entity classes (`Market`, `Vault`, `Position`) and offchain computations. The foundation of the rest of the stack. * **`@morpho-org/morpho-sdk/fetch`** - Viem-based fetchers for live on-chain data. * **`@morpho-org/morpho-sdk/abis`**, **`/addresses`**, **`/constants`**, **`/bundler`** - ABIs, deployment addresses, protocol constants, and Bundler3 / GeneralAdapter1 helpers. * **`@morpho-org/morpho-sdk/midnight-api`** - The Midnight HTTP API helpers (books, quotes, takeable offers, mempool validation). See the [Morpho SDK guide](/developers/sdks/morpho-sdk/) for the full surface. ## Where next [#where-next] * **Recommended starting point:** [Morpho SDK guide](/developers/sdks/morpho-sdk/) * **Vault deposits & withdrawals:** [Vault guide](/developers/sdks/morpho-sdk/vault/) * **Variable-rate lending & borrowing:** [Blue guide](/developers/sdks/morpho-sdk/blue/) * **Fixed-term lending:** [Midnight toolkit guide](/developers/sdks/morpho-sdk/midnight/) * **Source code:** [`morpho-org/sdks`](https://github.com/morpho-org/sdks) - the SDK monorepo containing [`@morpho-org/morpho-sdk`](https://github.com/morpho-org/sdks/tree/main/packages/morpho-sdk) and the [Midnight toolkit](https://github.com/morpho-org/sdks/tree/main/packages/midnight-sdk) * **Need help?** Reach out via the chat on **[help.morpho.org](https://help.morpho.org)**. --- # Morpho Apps Source: https://docs.morpho.org/get-started/resources/app-ecosystem This section details the critical applications developed by Morpho Association for vault configuration and ecosystem participation.

Consumer

### Main App [#main-app] [app.morpho.org](https://app.morpho.org/) **Purpose:** Interface for both lenders interacting with Morpho vaults and Borrowers interacting with Morpho Markets. **Key Features:** * View listed Morpho Markets & Morpho Vaults * Deposit, manage, and exit positions in Morpho Vaults * Enter, manage, and exit from borrow positions * Track vault performance metrics * Monitor position health and market statistics **Version: Production**

If the main App is not accessible

If you don't want to use the Main Morpho app ([https://app.morpho.org/](https://app.morpho.org/)) or if you are not able to access it, it is always possible to interact with Morpho via alternative apps, the [Fallback app](/get-started/resources/app-ecosystem/#fallback-app) or at contract level via explorers.

Morpho Warnings

Morpho has a warning system built into the Morpho API. These warnings surface potential risks that could impact markets or vaults on Morpho. Some warnings are also displayed in the main Morpho app. `Displayed in main app?` means the current main app has user-facing warning copy for that warning type on at least one relevant market or vault surface. Some app surfaces filter warnings by severity, and action-state warnings can affect buttons without rendering a warning icon. **If you consume warnings from the API:** * **The default response returns active warnings only.** Market and Vault V1 warnings have no `GREEN` level at all. Vault V2 defines one for passing checks, but it is not in the default response: `level_in` **replaces** the default `[YELLOW, RED]` filter rather than extending it, so `level_in: [GREEN]` hides every active warning. * The API decides the level. Key your logic off `level`, not `type` alone: a `custom` warning can be returned at either level. * **Market warnings do not propagate to vaults.** Only `oracle_unusable`, `oracle_price_derivation`, and (Vault V2 only) `bad_debt_unrealized` have vault-level counterparts, computed under the exposure rules below; other market warnings have no vault-side equivalent. * **Two warning types gate the deposit form in the main app:** `deposit_disabled` and `oracle_unusable`, both at RED. Either one alone blocks deposits, so check for both, not just `deposit_disabled`.
Variable Rate Market Warnings
| **Warning Type** | **Level** | **Displayed in main app?** | **Criteria / Threshold** | **Notes** | | ------------------------- | --------------------- | -------------------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `bad_debt_unrealized` | 🔴 RED | Yes | Unrealized bad debt / total supply assets >= 5% (500 bps), and at least USD 100 | Detects accumulating bad debt | | `not_whitelisted` | 🟡 YELLOW | Yes | Market is not listed | Markets are automatically listed/delisted when listed/delisted from a listed vault | | `oracle_price_derivation` | 🔴 RED | Yes | On-chain oracle price deviates from USD-derived price by >10x | Only for Morpho Chainlink / Chainlink V2 oracles with known pricing | | `oracle_unusable` | 🔴 RED | Yes | The oracle returns a price of 0, or has been reverting for at least 10 minutes | Applies to all oracle types; `metadata.type` is `zero_price` or `reverted`. Not raised when the market has no collateral asset or no oracle, or while the latest observation is stale | | `sustained_low_liquidity` | 🔴 RED | Yes | Zero available liquidity for 5 full consecutive days | Requires total supply > 0 and total supply not greater than total borrows. Idle markets (no collateral asset) are excluded | | `custom` | 🔴 RED
🟡 YELLOW | Yes | Operator-defined custom warning with arbitrary content | Level configured per entry |
Vault V1 Warnings
| **Warning Type** | **Level** | **Displayed in main app?** | **Criteria / Threshold** | **Notes** | | ------------------------- | --------------------- | -------------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | `short_timelock` | 🔴 RED | Yes | Timelock \< 3 days | Governance security | | `not_whitelisted` | 🟡 YELLOW | Yes | Vault is not listed | If vault was previously listed, will indicate a deprecation message | | `deposit_disabled` | 🔴 RED | No | No supply-queue allocation has remaining cap, or the vault is on the curated deposit-disable list | Not displayed as a warning icon; it can disable deposits in the main app | | `low_liquidity` | 🟡 YELLOW | Yes | Withdrawable liquidity \< 0.5% of total assets | Empty vaults (total assets = 0) are not flagged | | `oracle_unusable` | 🔴 RED | Yes | Exposure to markets with a RED `oracle_unusable` exceeds 10% of total assets | Exposure-gated, see the exposure note below. On its own, it also disables deposits in the main app | | `oracle_price_derivation` | 🔴 RED | Yes | Exposure to markets with a RED `oracle_price_derivation` exceeds 10% of total assets | Exposure-gated, see the exposure note below | | `custom` | 🔴 RED
🟡 YELLOW | Yes | Operator-defined custom warning with arbitrary content | Level configured per entry | Vault V1 has no bad-debt warning: bad debt is socialized across depositors as it is realized, so `bad_debt_unrealized` stays on the market.
Vault V2 Warnings
| **Warning Type** | **Level** | **Displayed in main app?** | **Criteria / Threshold** | **Notes** | | ------------------------- | --------------------- | -------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `not_whitelisted` | 🟡 YELLOW | Yes | Vault is not listed | If vault was previously listed, will indicate a deprecation message | | `timelock` | 🔴 RED | Yes | Protected functions are below the minimum timelock | Governance security | | `deposit_disabled` | 🔴 RED | No | Vault is on the curated deposit-disable list | Metadata-driven only: Vault V2 has no cap-derived equivalent. Not displayed as a warning icon; it can disable deposits in the main app | | `low_liquidity` | 🟡 YELLOW | Yes | Withdrawable liquidity \< 0.5% of total assets | Withdrawable counts `liquidity + forceDeallocatableLiquidity`, so a vault that can instantly reallocate is not flagged. Empty vaults (total assets = 0) are not flagged | | `bad_debt_unrealized` | 🔴 RED | Yes | Exposure to markets with a RED `bad_debt_unrealized` exceeds 10% of total assets | Exposure-gated, see the exposure note below | | `oracle_unusable` | 🔴 RED | Yes | Exposure to markets with a RED `oracle_unusable` exceeds 10% of total assets | Exposure-gated, see the exposure note below. On its own, it also disables deposits in the main app | | `oracle_price_derivation` | 🔴 RED | Yes | Exposure to markets with a RED `oracle_price_derivation` exceeds 10% of total assets | Exposure-gated, see the exposure note below | | `custom` | 🔴 RED
🟡 YELLOW | Yes | Operator-defined custom warning with arbitrary content | Level configured per entry | **Vault-level `oracle_unusable`, `oracle_price_derivation` (both vault versions) and `bad_debt_unrealized` (Vault V2) are gated on exposure.** The vault raises the warning when its **summed** exposure to every market carrying that warning at RED exceeds **10% of the vault's total assets**, counting both direct market allocations and pro-rata look-through of Vault V1 adapters. Below that threshold the vault carries no warning, though the market still does. Look-through stops at Vault V1: exposure held through a Vault V2 adapter is not traced, so a vault allocating into a nested Vault V2 reads as unaffected even when the nested vault holds a flagged market. To surface every affected vault, inspect its market exposure rather than relying on the vault-level warning alone. Refer to this [risk documentation](/learn/resources/risks/) to be sure you understand all the risks.

Tokens checks

Tokens are added to the Morpho database to enhance their use on the Morpho apps. Before being added to the token database, checks are performed to ensure compatibility. Here are the checks performed: * **Contract Existence** - A valid smart contract exists at the provided address * **ERC-20 Compliance** - The contract implements the core ERC-20 interface (name, symbol, decimals) * **Non-zero total supply** - The token’s total supply is greater than 0 * **DefiLlama Price Data** - The token has a recent price data available on DeFiLlama * **Logo Availability** - Token logo exists in Morpho database * **Not already listed** - The token is not already listed on the chain

Policy

Asset blocking policy
The Morpho protocol is a self-executing, permissionless, decentralized series of smart contracts that cannot be changed by anyone except the governance for given parameters. Morpho Association maintains one of many ways to interact with the Protocol (the Morpho Interface for instance), and as a non-profit association based in France is subject to certain laws and regulations. As such, there may be certain circumstances under which Morpho Association blocks access to certain markets and vaults through our the interface provided.
Criteria for Restricting Access on Morpho Association Interfaces
1. Morpho Association will block some components of the Protocol for the following reasons: 1. Risks to users. Morpho Association believes there are technical, fraud, or other risks that could significantly affect user safety. 2. Legal and regulatory requirements. 3. Fraud allegations. 2. Morpho Association *will* block individual or smart contract addresses that are subject to U.S. government sanctions or associated with certain types of illegal activity. 3. Morpho Association *may* block individual or smart contract addresses for any other reason at their discretion. For example, Morpho Association might have reason to believe that an address is associated with fraud, theft, or market manipulation, or that a smart contract has unacceptable security vulnerabilities.

Additional disclosures

Interface Warnings are created using information from third-party data providers. Morpho Association does not guarantee their accuracy and makes no representations or warranties about their relevance or currentness. The Morpho Protocol operates on a permissionless and decentralized basis, allowing Markets and Vaults to be established and administered by any third-party individual or entity. Any information provided by the Morpho Association on Vaults, Markets or any of their parameters does not constitute an endorsement of the Markets or Vaults and is provided solely for informational purposes. It does not represent an endorsement, recommendation, or any form of advice on how to engage with or utilize the Morpho Protocol.
Morpho Association Token Lists and Interface Warnings are not investment advice and should not be construed as such. Morpho Association strongly advise that before engaging in any financial, legal, technical, or other significant decisions related to the Morpho Protocol, you perform your own research (including, but not limited to, an analysis of the market or vault's structure, the assets involved, the parties responsible for its creation and management, and the potential risks and rewards associated with participation) and consult with an independent professional who is licensed and qualified in the relevant field. Such advice is crucial to ensure that your decisions are well-informed and take into account the specific nuances and complexities of your situation.

Slippage Considerations

When interacting with [Morpho Markets](/learn/concepts/blue/) and [Morpho Vaults](/learn/concepts/vault-v2/) through the Morpho Interface, slippage protection is implemented to safeguard users against unexpected price movements between transaction submission and execution. Understanding how slippage is handled is crucial for both users and developers.
Types of Slippage Protection
1. **Token Wrapping Operations** * Applied to: stETH → wstETH conversions, ERC4626 deposits (including Morpho Vaults deposits) * Fixed maximum slippage: 3 basis points (0.03%) 2. **Market Operations** * Formula: $$ e^{4 \cdot r_{\text{target}} \cdot d} - 1 $$ * Where: * **r\_target** is the target interest rate * **d** is the expected transaction duration (15 minutes for EOA, 45 minutes for Safe) 3. **Vault Operations** * Formula: $$ \left( 1 + r_{apy} \right)^{\frac{d}{yearInSeconds}} - 1 $$ * Where: * **r\_apy** is the Annual Percentage Yield * **d** is the expected transaction duration (15 minutes for EOA, 45 minutes for Safe) * **yearInSeconds** is the number of seconds in a year
Shares Conversion and Slippage
In operations involving asset-to-share conversions (e.g. borrowing, repaying, Morpho Vaults deposits/withdrawals), the interface accounts for worst-case slippage scenarios. This is particularly important in the following cases: * **Asset-Based Operations**: Borrowing, partial repayments, Morpho Vaults deposits/partial withdrawals, supplying/withdrawing collateral * **Shares-Based Operations**: Maximum repayments, Morpho Vaults deposits in rehypothecation, maximum Morpho Vaults withdrawals **Note**: Slippage can significantly impact transactions combining asset-based and shares-based operations for the same token.
Special Considerations for Morpho Vaults
Morpho Vaults ERC4626-compliant tokens are treated differently depending on the context: * As assets when supplied as collateral * As shares when supplied as deposits In rehypothecation, they are treated as shares during Morpho Vaults Deposit and as assets during Supply Collateral.

At which level is it implemented?

These slippage calculations are automatically applied in relevant `bundlerActions` to protect users from adverse price movements during transaction processing. ### Fallback App [#fallback-app] [fallback.morpho.org](https://fallback.morpho.org/) **Purpose:** Built for emergencies, this app prioritizes resilience over speed. This interface is for both lenders interacting with Morpho vaults and Borrowers interacting with Morpho Markets. **Key Features:** * View your Morpho Markets & Morpho Vaults positions * Exit positions from Morpho Vaults and Morpho Markets **Version: Production**
**Deploy your own independent Fallback App**

Why deploy your own?

The Morpho Fallback App can be self-hosted, giving you: * **Independence**: Access to your assets even if app.morpho.org is unavailable * **Censorship resistance**: No reliance on any single provider * **Version control**: Stay on a trusted version you've reviewed The app is fully static (just files, no server needed) and connects directly to public blockchain nodes.
***

Build and deploy your own

To deploy your own version, you'll need to build from the source code ([morpho-lite-apps repository](https://github.com/morpho-org/morpho-lite-apps)). This requires some familiarity with the command line, [Node.js](https://nodejs.org/) v18+ and [pnpm](https://pnpm.io/). The example below uses Pinata but other deployment options exist.
Step 1: Build the app
```bash git clone https://github.com/morpho-org/morpho-lite-apps.git cd morpho-lite-apps pnpm install pnpm run fallback-app:build ``` This creates the build output in apps/fallback/dist.
Step 2: Test locally (optional)
```bash npx serve apps/fallback/dist ``` Open [http://localhost:3000](http://localhost:3000) in your browser to verify the build works. This can also be used as a local-only deployment - no third parties required. Just run this command whenever you need access to the fallback app.
Step 3: Log to Pinata and upload
Go to [Pinata](https://pinata.cloud/) and sign up for a free account. * Log into Pinata dashboard * Go to the "Files" page, click on "+ Add" and choose "Folder Upload" * Select the entire apps/fallback/dist folder * Click "Upload"
Step 4: Access your app
After upload, Pinata gives you a CID (content identifier). Your app is now live at: [https://YOUR\_CID.ipfs.dweb.link](https://YOUR_CID.ipfs.dweb.link)
You can also use alternative gateways (different ways to access the same content):
| Gateway | URL Pattern | | ------------------ | --------------------------------- | | DWeb (Recommended) | `https://YOUR_CID.ipfs.dweb.link` | | IPFS.io | `https://ipfs.io/ipfs/YOUR_CID` | | Web3.Storage | `https://YOUR_CID.ipfs.w3s.link` |
***

Updating Your Deployment

Since IPFS content is immutable (never changes), updating means creating a new deployment: 1. From your morpho-lite-apps folder, get the latest code from the [morpho-lite-apps repository](https://github.com/morpho-org/morpho-lite-apps): `git pull origin main` 2. Rebuild 3. Upload the new `dist/` folder to Pinata (or your chosen host) 4. Use your new CID Your old CID continues working forever, so you can stay on a known version or upgrade when ready.
***

Alternative: Use a Community Deployment

If you prefer not to build your own, you can use an existing community-hosted version. The official [fallback.morpho.org](https://fallback.morpho.org/) app displays available IPFS CIDs that you can access directly through any gateway. A community deployment is also available at: **[https://bafybeihdvi6wgow3wk7mztnldoyyg5m2pmudslafry2ckelcalm2pw3wiq.ipfs.dweb.link](https://bafybeihdvi6wgow3wk7mztnldoyyg5m2pmudslafry2ckelcalm2pw3wiq.ipfs.dweb.link)**
Note: When using third-party deployments, you trust that the deployer built from the official source without modifications.
***

Security Best Practices

* **Verify the source**: Only build from the official repository ([morpho-lite-apps repository](https://github.com/morpho-org/morpho-lite-apps)) * **Review before trusting**: Check the code before deploying, especially if you're security-conscious * **Use HTTPS gateways**: Always access via `https://` to prevent tampering * **Safe by design**: The app never asks for your private keys - it only requests transaction signatures through your wallet
### Data Dashboard [#data-dashboard] [data.morpho.org](https://data.morpho.org/) **Purpose:** Provides on-chain data and analytics for the Morpho ecosystem. **Version:** Production ### Rewards [#rewards] [campaigns.morpho.org](https://campaigns.morpho.org/) **Purpose:** Monitor reward campaigns on Morpho, track rewards linked to a specific address, and claim rewards. Merkl is an application developed by a third party allowing to interact with rewards distributed on Morpho. The Morpho Association does not endorse or guarantee its functionality. Use at your own discretion.

Prime

### Curator App V2 [#curator-app-v2] [curator.morpho.org](https://curator.morpho.org/vaults) **Purpose:** Delivers specialized tools for Morpho Vault V2 curators to manage vault operations. **Key Features:** * Create and deploy new vaults V2 * Create and manage Fee Wrappers * Curate adapter listings and parameters * Configure vault settings and permissions * Set allocation caps, parameters and timelocks **Version:** Beta deployment with ongoing feature development **Implementation Note:** For actions not yet implemented in the Curator App, use direct interaction with vault contracts through verified sources such as Etherscan. Always verify contract addresses before interaction. See curate section [here](/curate/tutorials-v2/vault-creation/). ### Curator App V1 [#curator-app-v1] [curator-v1.morpho.org](https://curator-v1.morpho.org/) **Purpose:** Delivers specialized tools for Morpho Vault V1 curators to manage vault operations. **Key Features:** * Create and deploy new vaults V1 * Curate market listings and parameters * Configure vault settings and permissions * Set allocation caps, parameters and timelocks **Version:** Beta deployment with ongoing feature development **Implementation Note:** For actions not yet implemented in the Curator App, use direct interaction with vault contracts through verified sources such as Etherscan. Always verify contract addresses before interaction. See curate section [here](/curate/tutorials-v1/vault-creation/).

Other

### Oracle Portal [#oracle-portal] [oracles.morpho.dev](https://oracles.morpho.dev/) **Purpose:** Aid oracle configurations to ensure price feed accuracy and security. **Key Features:** * Decode oracle configurations * Verify price accuracy against reference sources * Test oracle configurations * Identify configuration issues **Version:** Production **Implementation Note:** Always verify oracle data configurations before deploying to production environments. Use this tool to: 1. Confirm correct asset pricing 2. Validate operational parameters 3. Test edge case scenarios ### Liquidation App [#liquidation-app] [liquidation.morpho.org](https://liquidation.morpho.org/) **Purpose:** Provides manual liquidation capabilities for unhealthy positions. **Key Features:** * Identify liquidatable positions * Execute manual liquidations * Calculate liquidation profitability * Monitor position health metrics **Version:** Production and requires supplemental automation **Implementation Note:** Develop and deploy automated liquidation bots for production environments. The liquidation app serves primarily as a backup mechanism when bots fail or for testing purposes. --- # Audits - Security Reviews Source: https://docs.morpho.org/get-started/resources/audits Morpho is known for its industry-leading security practices and follows a multi-faceted approach to security. Morpho security practices include formal verification, mutation tests, fuzzing, unit testing, and peer reviews that can be found within respective [Github repositories](https://github.com/morpho-org). External measures include professional security reviews, contests, and pre/post-deployment bounties. A whole article was dedicated to the [Morpho Security Framework](https://morpho.org/blog/morpho-blue-security-framework-building-the-most-secure-lending-protocol/). The Morpho apps and smart contracts have been audited extensively by a wide range of security firms, with every new app and feature undergoing audits before release. The full list of audits is available below. | Auditors | Scope | Date | Report | | :--------------------------------------------------------------------------------------- | :--------------------------------------------- | :----------------- | :------------------------------------------------------------------------------------------------------------------------------------ | | [Spearbit](https://spearbit.com/) | Morpho Midnight | 2026-07-02 | [Morpho: Midnight Competition Fixes Security Review](https://github.com/morpho-org/midnight/blob/main/audits/2026-07-02-spearbit.pdf) | | [Blackthorn](https://www.blackthorn.xyz/) | Morpho Midnight | 2026-07-02 | [Security Review for Morpho](https://github.com/morpho-org/midnight/blob/main/audits/2026-07-02-blackthorn.pdf) | | [Trust Security](https://trustsec.xyz/audits) | Morpho Midnight | 2026-07-02 | [Smart Contract Audit - Morpho Midnight](https://github.com/morpho-org/midnight/blob/main/audits/2026-07-02-trustsec.pdf) | | [Cantina Competition](https://cantina.xyz/) | Morpho Midnight | 2026-05-29 | [Morpho Midnight Security Review](https://github.com/morpho-org/midnight/blob/main/audits/2026-05-29-cantina-competition.pdf) | | [StErMi](https://x.com/StErMi) | Morpho Midnight | 2026-05-19 | [Morpho Midnight Security Review](https://github.com/morpho-org/midnight/blob/main/audits/2026-05-19-stermi.pdf) | | [Spearbit](https://spearbit.com/) | Morpho Midnight | 2026-05-13 | [Morpho Midnight Core (0de7070) Security Review](https://github.com/morpho-org/midnight/blob/main/audits/2026-05-13-spearbit.pdf) | | [Spearbit](https://spearbit.com/) | Morpho Midnight | 2026-04-07 | [Morpho: Midnight Security Review](https://github.com/morpho-org/midnight/blob/main/audits/2026-04-07-spearbit.pdf) | | [Certora](https://www.certora.com/) | Vault V2 - MarketV1AdapterV2 | 2025-15-12 | [certora-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Vault V2 - MarketV1AdapterV2 | 2025-04-12 | [spearbit-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Blackthorn](https://www.blackthorn.xyz/) | Vault V2 - MarketV1AdapterV2 | 2025-04-12 | [blackthorn-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Certora](https://www.certora.com/) | Vault V2 - Adapters Registry | 2025-04-12 | [certora-report](https://github.com/morpho-org/vault-v2-adapter-registries/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Vault V2 - Adapters Registry | 2025-09-11 | [spearbit-report](https://github.com/morpho-org/vault-v2-adapter-registries/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Vault V2 | 2025-11-08 | [spearbit-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [ChainSecurity](https://www.chainsecurity.com/) | Vault V2 | 2025-09-15 | [chainsecurity-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Blackthorn](https://www.blackthorn.xyz/) | Vault V2 | 2025-09-15 | [blackthorn-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Cantina Contest](https://cantina.xyz/competitions/523e1540-f8c3-45ae-9c5d-b6d35d3a326c) | Vault V2 | 2025-07-15 | [chainsecurity-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Zellic](https://www.zellic.io/) | Vault V2 | 2025-07-15 | [zellic-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Vault V2 | 2025-05-19 | [spearbit-report](https://github.com/morpho-org/vault-v2/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Morpho Blue Periphery - Preliquidation | 2024-10-29 | [spearbit-report](https://github.com/morpho-org/pre-liquidation/tree/main/audits) | | [ABDK Consulting](https://abdk.consulting/) | Morpho Blue Periphery - Preliquidation | 2024-11-01 | [ABDK-report](https://github.com/morpho-org/pre-liquidation/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Vault V1.1 | 2024-11-23 | [cantina-managed-review](https://github.com/morpho-org/metamorpho-v1.1/tree/main/audits) | | [Open Zeppelin](https://www.openzeppelin.com/) | Vault V1.1 | 2024-11-16 | [MetaMorpho Diff Audit](https://github.com/morpho-org/metamorpho-v1.1/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Morpho Blue Periphery - Public Allocator | 2024-03-11 | [cantina-managed-review](https://github.com/morpho-org/public-allocator/tree/main/audits) | | [Cantina Contest](https://cantina.xyz/competitions/d86b7f95-e574-4092-8ea2-78dcac2f54f1) | Morpho Blue | 2023-11 to 2023-12 | [cantina-competition](https://github.com/morpho-org/morpho-blue/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Morpho Blue | 2023-10-16 | [cantina-managed-review](https://github.com/morpho-org/morpho-blue/tree/main/audits) | | [Open Zeppelin](https://www.openzeppelin.com/) | Morpho Blue | 2023-10-13 | [open-zeppelin](https://github.com/morpho-org/morpho-blue/tree/main/audits) | | [Cantina Contest](https://cantina.xyz/competitions/8409a0ce-6c21-4cc9-8ef2-bd77ce7425af) | Vault V1 and Periphery | 2023-11 to 2023-12 | [cantina-competition](https://github.com/morpho-org/metamorpho/tree/main/audits) | | [Spearbit](https://spearbit.com/) | Vault V1 | 2023-11-16 | [cantina-managed-review](https://github.com/morpho-org/metamorpho/tree/main/audits) | | [Open Zeppelin](https://www.openzeppelin.com/) | Vault V1 - Morpho Blue - Morpho Periphery | 2023-11-16 | [open-zeppelin](https://github.com/morpho-org/metamorpho/tree/main/audits) | | [Omniscia](https://omniscia.io/) | MORPHO Token | 2022-06-15 | [omniscia-report](https://omniscia.io/morpho-specialized-token/) | | [Lexfo](https://www.lexfo.fr/en/) | Morpho-related DNS's & Morpho-related Github's | 2023-05-10 | [Lexfo\_250523.pdf](https://cdn.morpho.org/documents/Lexfo_250523.pdf) | | [Securing](https://www.securing.biz/) | \[Beta] Morpho Association dApp | 2022-04-04 | [MorphoDAO\_Frontend\_04042022.pdf](https://cdn.morpho.org/documents/MorphoDAO_Frontend_04042022.pdf) | --- # Morpho Interface Listing Policy Source: https://docs.morpho.org/get-started/resources/interface-listing-policy ## 1. What is this policy? [#1-what-is-this-policy] The Morpho protocol is permissionless. Anyone can deploy vaults and markets and interact with them directly onchain, without anyone's permission. This policy applies exclusively to the app at morpho.org and any other front-ends operated by Morpho Association. Listing is the decision to *display* a curator, vault, market, or token in that interface. Delisting is the decision to stop displaying it. Crucially, delisting is a UI-level decision, not a protocol-level one. A vault or market that is never listed, or is later delisted, continues to operate exactly as it was programmed on the blockchain. Its smart contracts remain immutable and fully accessible; users retain the ability to deposit, withdraw, and interact with these assets directly via on-chain transactions, block explorers, or any other alternative interface, regardless of the Morpho Association's decision to display or hide it from [morpho.org](http://morpho.org/). ## 2. How listings works [#2-how-listings-works] Listing is a discovery decision. It is **not**: * an endorsement, recommendation, or approval of a curator, vault, market, strategy, or asset; * a risk assessment, audit, rating, or guarantee of safety, solvency, or performance; * investment, financial, legal, or tax advice; * a representation that a listed party is trustworthy, competent, solvent, or law-abiding; * a warranty of any kind. Morpho Association does not custody user funds, does not manage any vault, and does not assume risk on behalf of users. Risk management is fully externalized to curators, who are independent third parties responsible for their own strategy design, configuration, monitoring, and conduct. **Users are solely responsible for their own due diligence and for any decision to deposit, borrow, or otherwise transact.** Any warnings, banners, or informational labels shown in the interface are provided for convenience only. We do not review every vault and market individually the way a centralized platform would. Instead, the interface is organized around **curators**: 1. A curator is listed if it meets the standards outlined in this policy. 2. Vaults from a listed curator become eligible for listing if they meet our technical requirements. 3. Markets are surfaced when a listed vault allocates to them. 4. Assets are added to our token database so they render correctly and so warning logic can run. ## 3. Curator listing [#3-curator-listing] A curator that wants to appear in the interface submits a listing application. Morpho does not prescribe how a curator should manage risk or run its strategy; that responsibility stays with the curator. The application instead asks a curator to set out its own approach across three areas, so we can form a basic, point-in-time view of whether it presents as a competent, accountable operator: * **Value and yield** — what the vaults do, which assets and chains they target, the yield rationale, and why they add something the ecosystem does not already have. * **Risk mitigation** — its approach to oracle selection, liquidation paths, monitoring, incident response, and continuity planning. * **Curator profile** — track record, references, team structure, key-person risk, and operational and security practices. What we look for in these answers is seriousness and operational rigor at the time of listing: evidence that the curator is a competent, accountable operator. This does not represent a guarantee. A curator that clears it can still be negligent, get compromised, or fail in basic ways. ### 3.1 Minimum requirements [#31-minimum-requirements] The following are **necessary but not sufficient** conditions. Meeting them does not entitle any applicant to a listing. * **Identifiable and accountable operator.** The curator must be an identifiable team or entity with a verifiable point of contact. * **Verifiable track record and references.** Claims about prior deployments, managed capital, and experience must be checkable, and references must be reachable and must check out. * **Security and operational baseline.** A sound owner/curator/guardian address architecture, appropriate use of multisig, a stated private-key management policy, and no unmitigated single points of failure. * **Credible capital plan.** A realistic and evidenced plan demonstrating long-term commitment to the strategy and a genuine capital or seed commitment where required. * **Technical conformance of the vaults to be listed.** ### 3.2 Discretionary evaluation [#32-discretionary-evaluation] Beyond the minimums, listing is a **discretionary** decision. Morpho Association evaluates each application holistically across qualitative dimensions that include, without limitation, operational competence, credibility of execution, quality and originality of the strategy, growth prospects, security posture, and overall contribution to the ecosystem relative to the curators already listed. Because these are qualitative judgments about what the interface surfaces, they are not reducible to a fixed checklist or numeric threshold, and no applicant is entitled to a listing by virtue of satisfying any subset of criteria. ### 3.3 Outcomes and process [#33-outcomes-and-process] An application may result in one of the following: **acceptance**, and progression to a listing process; **a request for improvements**, with the option to resubmit; or **decline**. ### 3.4 Reservation of rights [#34-reservation-of-rights] Morpho Association may, **at its sole and absolute discretion, and at any time**, decline any application, defer a decision, request changes, or list, restrict, or remove any curator, vault, market, or token, with or without prior notice and **without any obligation to provide a reason**. Nothing in this policy creates any right to be listed, any obligation on Morpho Association to list or to continue listing anything, or any entitlement to an explanation of a listing decision. The criteria described in this policy are illustrative and non-exhaustive. Morpho Association may weigh additional factors and may apply, change, or waive criteria case by case. Publishing this policy does not limit that discretion. Because the protocol is permissionless, a decision not to list, or to delist, never restricts anyone's ability to interact with the protocol directly on-chain. It affects only what the Morpho Association interface chooses to display. ## 4. Vault, market, and token eligibility [#4-vault-market-and-token-eligibility] Vault listing is primarily an automated "meets best practices" gate, checked through the listing application. At a high level, a vault must be a Morpho Vault V2 that routes capital exclusively through Morpho-approved adapters — meaning it uses the official Morpho adapter registry and has abdicated the ability to change it — and must be created from official Morpho factories. It must satisfy the applicable timelock minimums on sensitive functions (or have permanently disabled them), must be properly seeded and must not impersonate Morpho in its name or symbol. Its owner and curator must be recognized for the target chain. Markets appear in the interface when a listed vault allocates to them and their configuration parameters are set. Tokens are listed only when they are valid contracts, meet basic standards, pass security screening, and have reliable pricing data. The authoritative, up-to-date technical checks are enforced by the listing application itself, which is the source of truth if this summary and the tooling ever diverge. ## 5. Ongoing enforcement [#5-ongoing-enforcement] Listing is not permanent. We continuously monitor listed vaults and markets for configuration health, timelock compliance, asset and oracle sanity, and curator recognition status. Monitoring feeds user-facing warnings, internal alerts, and listing-state changes. ## 6. Warnings [#6-warnings] The interface may display severity-graded warnings on vaults and markets — for example, to flag an unrecognized configuration, a stale or deviating oracle price relative to a reference, accumulating bad debt, an insufficient timelock, or an unrecognized asset. Warnings are informational aids to help users exercise their own judgment. They are best-effort signals, not risk assessments, and they can be incomplete or lag behind reality. A vault showing no warning is not, for that reason, safe. A vault can be delisted without ever carrying a warning, and a warning can sit on a vault that stays listed. ## 7. Delisting [#7-delisting] Morpho Association may remove a listing at any time. Grounds include, without limitation: failing a mandatory technical check (for example, a broken deposit path, a missing required timelock, an invalid adapter configuration, or a missing protective deposit); a critical risk signal (for example, an incorrect oracle configuration or an unrecognized core asset); a curator or owner ceasing to be recognized for a chain; prolonged inactivity; a breach of the listing agreement or of this policy; conduct that in our judgment poses a risk to users, to the ecosystem, or to Morpho Association; and any of the discretionary grounds mentioned above. In the event of a delisting, users may still interact with the protocol directly on-chain to withdraw or manage their positions. **Prolonged inactivity.** A vault is treated as inactive where it has been listed for more than 3 months and holds less than $100,000 in total deposits. Inactive vaults may be delisted at any time. Where all of a curator's vaults have been delisted, the curator is no longer displayed on [app.morpho.org](https://app.morpho.org/). ## 8. Changes [#8-changes] Morpho Association may amend this policy at any time, effective on publication, without individual notice. ## 9. Disclaimers [#9-disclaimers] The Morpho protocol is permissionless, non-custodial infrastructure. The interface operated by Morpho Association is one means of accessing it and is provided on an "as is" and "as available" basis, without warranties of any kind. Nothing in this policy or in the interface constitutes an endorsement, a risk assessment, or investment, financial, legal, or tax advice, and nothing creates a fiduciary, advisory, or agency relationship between Morpho Association and any user or curator. Listing, non-listing, and delisting decisions are made at Morpho Association's sole discretion. To the maximum extent permitted by law, Morpho Association accepts no liability for any loss arising from the use of the interface or from any listing decision. Users transact at their own risk and are responsible for their own due diligence and for compliance with the laws applicable to them. --- # Morpho Research Source: https://docs.morpho.org/get-started/resources/whitepapers Morpho is a research-driven protocol designed to make lending more open, efficient, and resilient. Research acts as the bridge between ideas and execution, combining mathematics and software implementation. Every single mechanism design decision is made after long periods of research conducted by diverse teams around the world. *** ### Midnight Whitepaper [#midnight-whitepaper] **Authors:** Morpho Association **Published:** May 22, 2026 [Read paper](https://morpho.org/whitepapers/midnight-whitepaper.pdf) *** ### Morpho Token Whitepaper [#morpho-token-whitepaper] **Authors:** d\&a partners **Published:** April 20, 2026 [Read paper](https://morpho.org/whitepapers/token-whitepaper.pdf) *** ### Agents' Behavior and Interest Rate Model Optimization in DeFi Lending [#agents-behavior-and-interest-rate-model-optimization-in-defi-lending] **Authors:** Charles Bertucci, Louis Bertucci, Mathis Gontier Delaunay, Olivier Gueant, Matthieu Lesbre **Published:** April 23, 2024 [Read paper](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=4802776) *** ### Morpho Blue Whitepaper [#morpho-blue-whitepaper] **Authors:** Morpho Association **Published:** October 1, 2023 [Read paper](https://github.com/morpho-org/morpho-blue/blob/main/morpho-blue-whitepaper.pdf) *** ### The Paradox of Adversarial Liquidation in Decentralized Lending [#the-paradox-of-adversarial-liquidation-in-decentralized-lending] **Authors:** Samuel N. Cohen, Marc Sabate Vidales, Lukasz Szpruch and Mathis Gontier Delaunay **Published:** August 14, 2023 [Read paper](https://www.simtopia.ai/paradox.pdf) --- # Variable Rate Market (Morpho Blue) Source: https://docs.morpho.org/learn/concepts/blue
## What is a Variable Rate Market? [#what-is-a-variable-rate-market] A variable rate market is a primitive lending pool that pairs one collateral asset with one loan asset. Each market is isolated (meaning risks are contained within each individual market), immutable (cannot be changed after deployment), and will persist as long as the blockchain it deployed on is live. This design ensures predictable behavior and eliminates systematic for lenders and borrowers. Creating a Morpho Market is **permissionless**. ## Key Features [#key-features] * **Simple Structure**: One collateral asset, one loan asset per market * **Permanent Parameters**: Once created, rules never change * **Isolated Risk**: Each market operates independently * **Permissionless**: New market doesn’t require governance vote to be created - more [here](/learn/concepts/blue/#permissionless-market-creation) * **Transparent Rules**: Clear conditions for lending and borrowing ## Market Identification [#market-identification] On the Morpho apps, markets follow this naming format: `LoanAsset | CollateralAsset (LLTV%)` For example: `USDC | cbBTC (86%)` The oracle and IRM addresses are shown on the market page. ## The Five Parameters [#the-five-parameters] 1. **Collateral Asset** that should be [ERC20 compliant](https://docs.openzeppelin.com/contracts/4.x/erc20) (except that it can omit return values on `transfer` and `transferFrom`.) 2. **Loan Asset** sharing same properties as collateral asset. However, the Loan asset should not be [ERC4626 compliant](https://docs.openzeppelin.com/contracts/4.x/erc4626). 3. **LLTV (Liquidation Loan-To-Value)**: Maximum borrowing percentage before liquidation risk. E.g: LLTV of 80% means for a collateral value equivalent of $100, the maximum one can borrow in value is $80. If above like $80.0001, the position is liquidatable. 4. **Oracle**: Smart contract address pricing the collateral against the loan asset. 5. **IRM (Interest Rate Model)**: Smart contract address containing the formula for determining interest rate paid by borrowers. ## Governance-Approved LLTV & IRM [#governance-approved-lltv--irm] ### LLTVs [#lltvs] | LLTV (%) | Solidity Values (scaled by 1e18) | | -------- | -------------------------------- | | 0 | 0 | | 38.5 | 385000000000000000 | | 62.5 | 625000000000000000 | | 77.0 | 770000000000000000 | | 86.0 | 860000000000000000 | | 91.5 | 915000000000000000 | | 94.5 | 945000000000000000 | | 96.5 | 965000000000000000 | | 98.0 | 980000000000000000 | ### IRM [#irm] The only Interest Rate Model (IRM) that has been governance-approved is the [AdaptiveCurveIRM](/learn/concepts/irm/). ## Permissionless market creation [#permissionless-market-creation] A distinctive feature of Morpho is permissionless market creation: the protocol allows users to create isolated markets consisting of the five aforementioned parameters. This a departure from the existing paradigm and traditional lending platforms which: 1. Require governance approval for asset listing and parameter changes. 2. Pool assets into a single lending pool, sharing risk across the entire protocol. In Morpho, each parameter is selected at market creation and persists in perpetuity. Or, in other words, are immutable. The LLTV and interest rate model must be chosen from a set of options approved by Morpho Governance. ## Core Interactions [#core-interactions] * **Supply**: Lenders deposit loan assets into a specific market to earn interest. * **Borrow**: Borrowers supply collateral to the same market and borrow loan assets against it, up to the LLTV limit. * **Withdraw**: Lenders can withdraw their supplied assets and accrued interest, provided there is enough liquidity in the market. * **Repay**: Borrowers can repay their loan to reclaim their collateral. * **Liquidate**: If a borrower's position exceeds the LLTV, anyone can liquidate it by repaying a portion of the debt in exchange for a discounted portion of the collateral. --- # Morpho Bundlers Source: https://docs.morpho.org/learn/concepts/bundlers
## What are Bundlers? [#what-are-bundlers] Bundlers are smart contracts that allow you to combine multiple actions into a single transaction. Instead of executing several transactions in sequence-each requiring confirmation and gas fees-bundlers let you do everything at once. Bundler3 is Morpho's most advanced bundler, integrated directly into the Morpho interface. This means users will be able to perform complex operations with just one click. The specs can be found [here](/get-started/resources/contracts/bundlers). ## What Bundler3 Allows You To Do [#what-bundler3-allows-you-to-do] With Bundler3, you can: 1. **Streamline Complex Workflows** - Complete lending, borrowing, swapping, and more in one transaction 2. **Save on Gas Fees** - Pay for one transaction instead of many 3. **Avoid Failed Transactions** - If any part of your operation would fail, the entire transaction reverts safely 4. **Perform Advanced Operations** - Combine actions that normally require multiple steps. ## Practical Examples: [#practical-examples] * **One-Click Leverage**: Convert ETH to wstETH, deposit as collateral, and borrow USDC-all in one transaction * **Instant Refinancing**: Borrow from one market, repay another, and adjust collateral positions simultaneously * **Portfolio Rebalancing**: Withdraw, swap, and redeposit assets across multiple markets without waiting for confirmations --- # Curator Source: https://docs.morpho.org/learn/concepts/curator
A **Curator** in the Morpho ecosystem is an entity or individuals who configures vault risk parameters and liquidity allocation rules, abstracting risk curation decisions away from depositors. Curators are specifically responsible for the strategic [curation of Vault V2](/learn/concepts/vault-v2/), making key decisions about which adapters and underlying markets to enable, how capital is allocated, and how risk is curated within the vault structure. Their expertise directly impacts vault performance and security. ## The Curator's Responsibilities [#the-curators-responsibilities] A Curator's duties revolve around high-level strategic decisions and risk curation across Vault V2 configurations: * **Strategic Curation**: Defining the vault's overall allocation strategy. This includes selecting which protocols, markets, or asset types are permissible for allocation. * **Risk Parameterization**: Setting and maintaining the risk boundaries for the vault. This involves defining exposure limits (caps) to various risk factors to protect depositor capital. * **Appointing Allocators**: In Vault V2, the Curator is responsible for appointing the `Allocator(s)` who will execute allocations within the established risk framework. * **Setting Core Vault Mechanics**: In Vault V2, the Curator also controls crucial mechanisms like fees and compliance gating. Crucially, nearly all of a Curator's significant actions are subject to a **timelock**, providing transparency and giving depositors a window to exit if they disagree with a proposed change. ## The Curator's Toolkit in Vault V2 [#the-curators-toolkit-in-vault-v2] Vault V2 gives curators a flexible set of controls for portfolio construction and risk management: * **Protocol selection via adapters**: Curators decide which approved adapters can route vault capital. * **Granular risk curation**: Absolute and relative caps can be applied to abstract risk identifiers such as collateral, oracle, or protocol exposure. * **Yield controls**: Allocators can set `maxRate` constraints while adapters report current asset values back to the vault. * **Fee configuration**: Curators set performance and management fees together with the corresponding recipients. * **Compliance controls**: Optional gate contracts can enforce onchain deposit, withdrawal, and transfer rules. ## Curation within the Vault Role System [#curation-within-the-vault-role-system] The Curator role is designed to be distinct from other vault management functions, creating a clear separation of duties. * The **Owner** is the ultimate authority, with the power to appoint the `Curator` and `Sentinels`. The Owner's role is primarily administrative. * The **Curator** defines the *strategy* and *risk boundaries*. They decide **what** is possible within the vault (which protocols, what max exposure). * The **Allocator** is the *active execution role*. Appointed by the Curator, they operate **within** the boundaries set by the Curator, deciding **how** to allocate liquidity among the permitted options. * The **Sentinel** (the evolution of V1's `Guardian`) is a *safety mechanism*. It can reactively reduce risk by decreasing caps or deallocating assets, and it can revoke pending timelocked actions from the Curator, acting as a crucial check on their power. This structure ensures that strategic risk curation is separate from daily portfolio management (Allocation), leading to a more robust and secure system. --- # Flash Loans Source: https://docs.morpho.org/learn/concepts/flashloans Flash loans are a powerful DeFi primitive that allow users to borrow assets without collateral, as long as the borrowed amount is returned within the same transaction block. ## What Are Flash Loans in Morpho? [#what-are-flash-loans-in-morpho] Morpho's flash loans are similar to other DeFi protocols where these: * Allow borrowing without prior collateral * Require repayment within the same transaction * Execute in a single block * Are primarily meant for developers and advanced users ## How Morpho Flash Loans Work [#how-morpho-flash-loans-work] The core flash loan functionality is implemented through the `flashLoan` function in the Morpho contract with a corresponding callback mechanism. ### The Flash Loan Flow in Morpho [#the-flash-loan-flow-in-morpho] 1. **Initiation**: A user contract calls `morpho.flashLoan(token, amount, data)` (refer [here](https://github.com/morpho-org/morpho-blue/blob/main/test/forge/integration/CallbacksIntegrationTest.sol#L81) for the testing suite example) 2. **Asset Transfer**: Morpho transfers the requested token amount to the calling contract 3. **Callback Execution**: Morpho calls `onMorphoFlashLoan(amount, data)` on the caller contract ([here](https://github.com/morpho-org/morpho-blue/blob/main/test/forge/integration/CallbacksIntegrationTest.sol#L63) for the test example) 4. **Execution of Logic**: The user's contract executes its intended operations 5. **Repayment**: The user's contract must approve Morpho to pull back the borrowed amount 6. **Completion**: Morpho pulls the assets back from the caller contract If at any point the flow fails (especially if the repayment fails), the entire transaction reverts. ## Implementing a Flash Loan in Morpho [#implementing-a-flash-loan-in-morpho] To use a flash loan with Morpho, you need to: 1. Create a contract that implements the `IMorphoFlashLoanCallback` interface 2. Implement the `onMorphoFlashLoan` function that will handle your logic 3. Ensure your callback function approves the Morpho contract to pull back the borrowed amount ## Flash Loan Use Cases with Morpho [#flash-loan-use-cases-with-morpho] 1. **Arbitrage**: Execute trades across different protocols to profit from price discrepancies 2. **Collateral Swaps**: Replace one collateral type with another in a single transaction 3. **Self-Liquidation**: Liquidate your own position to avoid liquidation penalties 4. **Leverage Positions**: Flash loans can simplify leveraged deposit workflows by avoiding onchain looping. Instead of repeatedly borrowing and re-depositing to build leverage, you can use a flash loan to achieve your exact target leverage in a single transaction with more granular control 5. **Flash Actions**: Combine multiple Morpho operations in a single transaction ## Security Considerations for Morpho Flash Loans [#security-considerations-for-morpho-flash-loans] 1. **Transaction Atomicity**: If your callback fails to approve the repayment, the entire transaction will revert 2. **Contract Security**: Never leave assets in your flash loan contract permanently 3. **Reentrancy**: Be careful about calling external contracts within your flash loan logic 4. **Gas Management**: Flash loans are complex operations that consume significant gas ## Example Implementation [#example-implementation] The [FlashBorrowerMock.sol](https://github.com/morpho-org/morpho-blue/blob/main/src/mocks/FlashBorrowerMock.sol) contract in Morpho's repository provides a simple example of how to implement a flash loan contract: ```solidity // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; import {IERC20} from "./interfaces/IERC20.sol"; import {IMorpho} from "../interfaces/IMorpho.sol"; import {IMorphoFlashLoanCallback} from "../interfaces/IMorphoCallbacks.sol"; contract FlashBorrowerMock is IMorphoFlashLoanCallback { IMorpho private immutable MORPHO; constructor(IMorpho newMorpho) { MORPHO = newMorpho; } function flashLoan(address token, uint256 assets, bytes calldata data) external { MORPHO.flashLoan(token, assets, data); } function onMorphoFlashLoan(uint256 assets, bytes calldata data) external { require(msg.sender == address(MORPHO)); address token = abi.decode(data, (address)); IERC20(token).approve(address(MORPHO), assets); // Add your logic here. // e.g. swap, liquidate, leverage, and much more... } } ``` This minimal implementation demonstrates the key components: * **Constructor**: Stores the Morpho contract address * **flashLoan function**: External entry point that initiates the flash loan * **onMorphoFlashLoan callback**: Implements the required callback interface where you add your custom logic (arbitrage, swaps, leverage, etc.) * **Security check**: Verifies the caller is the Morpho contract * **Repayment approval**: Approves Morpho to pull back the borrowed assets ## Morpho-Specific Callbacks [#morpho-specific-callbacks] Morpho implements a broader callback system: * `IMorphoLiquidateCallback`: For liquidation operations * `IMorphoRepayCallback`: For repayment operations * `IMorphoSupplyCallback`: For supply operations * `IMorphoSupplyCollateralCallback`: For supplying collateral This comprehensive callback system allows for more complex transaction patterns beyond simple flash loans, such as the "Flash Actions" test which combines supply, borrow, repay, and withdraw operations in a single transaction flow. [https://dune.com/morpho/morpho-blue-dashboard#flashloans](https://dune.com/morpho/morpho-blue-dashboard#flashloans) --- # Interest Rate Model Source: https://docs.morpho.org/learn/concepts/irm
Morpho is an Interest Rate Model (IRM) agnostic protocol, meaning it can support any interest rate model for its markets. In Morpho, the interest borrowers pay in a given market is defined by the IRM chosen at market creation among a governance-approved set. The only IRM that has been governance-approved is the [AdaptiveCurveIRM](#the-adaptivecurveirm), which is described in more detail later in this page. ## Understanding Borrow and Supply APY [#understanding-borrow-and-supply-apy] The **Annualized Percentage Yield (APY)** is a critical metric that standardizes interest rates over a one-year period by accounting for compounding. In the context of lending protocols, two key APYs are: * **Borrow APY:** This reflects the effective annual interest cost that borrowers incur. It is derived from the instantaneous interest rate provided by the chosen Interest Rate Model (IRM). Essentially, the Borrow APY tells borrowers how much they will pay on an annual basis for borrowing assets. * **Supply APY:** This indicates the effective annual yield that lenders receive on their supplied assets. It is calculated by adjusting the Borrow APY based on the market's utilization rate and any applicable fees. The Supply APY, therefore, not only factors in the raw interest rate but also considers the portion of the rate that is passed on to suppliers after accounting for the market's fee (currently no fees are activated thus the fee equals). ### How Are These APYs Calculated? [#how-are-these-apys-calculated] ### Borrow APY Calculation [#borrow-apy-calculation] The Borrow APY is obtained by compounding the per-second borrow rate over the entire year. The formula is: $$ \text{borrowAPY} = \left(e^{\left(\text{borrowRate} \times \text{secondsPerYear}\right)} - 1\right) $$ Here, `borrowRate` is the rate provided by the IRM and `secondsPerYear` equals 31,536,000. ### Supply APY Calculation [#supply-apy-calculation] The Supply APY is obtained by adjusting the Borrow APY like this: $$ \text{supplyAPY} = \text{borrowAPY} \times \text{utilization} \times (1 - \text{fee}) $$ Where: * **Utilization:** Is the ratio of the total borrowed assets to the total supplied assets. * **Fee:** The market's fee determined by governance - currently no fees are applied. (Scaled by [WAD: 1e18](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IMorpho.sol#L94C36-L94C50)) Below is a basic typescript snippet showcasing the rate calculus with a rate returned by the IRM of `1512768697` ```typescript /** * Calculate borrow APY using the exponential formula: exp(rate * seconds) - 1 * Where: * - rate is the interest rate per second (in Wei with 18 decimals) * - seconds is the number of seconds (typically for a year: 31536000) * * @param ratePerSecond Interest rate per second in Wei (18 decimals) * @param seconds Number of seconds (typically a year: 31536000) * @returns The borrow APY as a decimal (e.g., 0.0489 for 4.89%) */ function calculateBorrowRate(ratePerSecond: string, seconds: number): number { // Convert rate to a number with 18 decimal precision const rate = Number(ratePerSecond) / 1e18; // Calculate exp(rate * seconds) - 1 const result = Math.exp(rate * seconds) - 1; return result; } /** * Calculate supply APY based on borrow APY, utilization, and fee * Formula: supplyAPY = borrowAPY * utilization * (1 - fee) * * @param borrowAPY The calculated borrow APY as a decimal (e.g., 0.0489 for 4.89%) * @param utilization The utilization rate as a decimal (e.g., 0.8 for 80%) * @param fee The fee rate in Wei (18 decimals, e.g., "0" for no fee) * @returns The supply APY as a decimal (e.g., 0.0416 for 4.16%) */ function calculateSupplyAPY( borrowAPY: number, utilization: number, fee: string ): number { // Convert fee from Wei to decimal (1e18 denominator) const feeRate = Number(fee) / 1e18; // Calculate supply APY using the formula: borrowAPY * utilization * (1 - fee) return borrowAPY * utilization * (1 - feeRate); } // Shared example values const secondsInYear = 31536000; const ratePerSecond = "1512768697"; // Rate per second in Wei (18 decimals) const utilization = 0.85; // 85% utilization const fee = "0"; // No fee (0 in Wei / 18 decimals) // Calculate borrow APY const borrowAPY = calculateBorrowRate(ratePerSecond, secondsInYear); // Calculate supply APY const supplyAPY = calculateSupplyAPY(borrowAPY, utilization, fee); // Display results console.log(`Borrow APY: ${(borrowAPY * 100).toFixed(2)}%`); console.log( `Supply APY: ${(supplyAPY * 100).toFixed(2)}% at ${( utilization * 100 ).toFixed(0)}% utilization with ${(Number(fee) / 1e18) * 100}% fee` ); ``` The output here is: ``` Borrow APY: 4.89% Supply APY: 4.15% at 85% utilization with 0% fee ``` ### Resources [#resources] For a deeper dive into the mechanics and the code behind these calculations, refer to the following resources: * [**Interest Rate Model Repository**](https://github.com/morpho-org/morpho-blue-irm): Explore the implementation and the smart contracts that power the IRM. * [**IRM Interface Documentation**](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IIrm.sol): Review the interface definitions and understand how different IRMs integrate into the system. * [**Introducing the AdaptiveCurveIRM**](https://morpho.org/blog/introducing-the-adaptivecurveirm-efficient-and-autonomous/) article This integrated approach ensures that both borrowers and suppliers have a clear understanding of the cost of borrowing and the returns on lending, all derived from a robust and governance-approved interest rate model. Initially, this set is composed of one immutable IRM, the AdaptiveCurveIRM. ## The AdaptiveCurveIRM [#the-adaptivecurveirm] ### Overview [#overview] The AdaptiveCurveIRM is engineered to maintain the ratio of borrowed assets over supplied assets, commonly called utilization, close to a target of 90%. In Morpho, the collateral supplied is not rehypothecated. Removing this systemic risk removes the liquidity constraints imposed by liquidation needs. It enables more efficient markets with higher target utilization of capital and lower penalties for illiquidity, resulting in better rates for both lenders and borrowers. As with every parameter of a Morpho Market, the IRM address is immutable. This means that neither governance nor market creators can change it at any given time. As such, the AdaptiveCurveIRM is designed to adapt autonomously to market conditions, including changes in interest rates on other platforms and, more broadly, any shifts in supply and demand dynamics. Its adaptability enables it to perform effectively across any asset, market, and condition, making it highly suitable for Morpho's permissionless market creation. ### How It Works [#how-it-works] The model can be broken down into two complementary mechanisms: 1. **The Curve Mechanism** This mechanism is akin to the interest rate curve in traditional lending pools. It manages short-term utilization effectively, maintaining capital efficiency while avoiding excessively high utilization zones that could lead to liquidity issues.
Curve Mechanism 1
$$ r_{90\%} \text{ is the target rate at utilization target } u_{target}=0.9 $$ Example with the interest rate currently at 4% at $$ u_{target}=0.9 $$ * If utilization rate goes to 100% following a market event, the interest rate will instantly go to 16% in this example (x4) * At the opposite, if a market event bring the utilization rate to 0%, the interest rate will instantly go to 1% in this example (/4) 2. **The Adaptive Mechanism** This mechanism fine-tunes the curve over time to keep the range of rates in sync with market dynamics. It achieves this by adjusting the value of the target rate, which in turn shifts the entire curve: * When utilization exceeds the target, the curve continuously shifts upward. This incentivizes loan repayment and thus decreases utilization. * When utilization falls below the target, the curve continuously shifts downward. This incentivizes borrowing and thus increases utilization.
Curve Mechanism 1
The speed at which the curve adjusts is determined by the distance of current utilization to the target: the further it is, the faster the curve shifts. This incremental adjustment of the curve allows for rate exploration, ultimately stabilizing when the interest rate at the target utilization aligns with the market equilibrium. Some examples are given below about the target $$ r_{90\%} $$ * If the utilization remains at 45%, it will progressively decrease until it is divided by 2 after 10 days. * If the utilization remains at 95%, it will progressively increase until it doubles after 10 days. * If the utilization remains at 100%, it will progressively increase until it doubles after 5 days. This is the maximum speed at which it can move. Here's a video showing how the two mechanisms combine to adjust interest rates:
For more on the AdaptiveCurveIRM, explore the [technical reference](/get-started/resources/contracts/irm). --- # Liquidation on Morpho Source: https://docs.morpho.org/learn/concepts/liquidation
## Overview [#overview] Liquidation is a critical mechanism in the Morpho protocol that protects lenders' capital by ensuring borrowers maintain healthy collateral positions. Morpho liquidation is built into the protocol core and allows full or partial position liquidation when a borrower's Loan-To-Value (LTV) exceeds the Liquidation Loan-To-Value (LLTV) threshold. ## Understanding Loan-To-Value (LTV) [#understanding-loan-to-value-ltv] Before exploring liquidation, it's essential to understand how LTV is calculated and what it represents. ### How to Calculate LTV [#how-to-calculate-ltv] The Loan-To-Value (LTV) ratio is a key risk metric that measures the proportion of debt relative to collateral value. To calculate the LTV of a position on Morpho, use the following formula: $$ \text{LTV} = \frac{\text{BORROWED\_AMOUNT}}{\text{COLLATERAL\_VALUE\_IN\_LOAN\_TOKEN}} \times 100\% $$ * **`BORROWED_AMOUNT`** The amount of borrowed assets of the user (in token base units) * **`COLLATERAL_VALUE_IN_LOAN_TOKEN`** The value of the collateral in terms of the loan token The collateral value in loan token units is calculated as: $$ \text{COLLATERAL\_VALUE\_IN\_LOAN\_TOKEN} = \frac{\text{COLLATERAL\_AMOUNT } \times \text{ ORACLE\_PRICE}}{\text{ORACLE\_PRICE\_SCALE}} $$ * **`COLLATERAL_AMOUNT`** The amount of collateral assets provided by the user (in token base units) * **`ORACLE_PRICE`** The oracle price returned by the oracle of the market (scaled by ORACLE\_PRICE\_SCALE) * **`ORACLE_PRICE_SCALE`** A scaling factor of 10^36 used by the protocol for price normalization ### Health Factor [#health-factor] The Health Factor is another crucial metric that indicates how close a position is to liquidation: $$ \text{HEALTH\_FACTOR} = \frac{\text{COLLATERAL\_VALUE\_IN\_LOAN\_TOKEN} \times \text{LLTV}}{\text{BORROWED\_AMOUNT}} $$ * **`LLTV`** The Liquidation Loan-To-Value threshold set for the market (e.g., 0.86 or 86%), expressed as a WAD (10^18 scaled value, like 860000000000000000) A position is healthy when the Health Factor is greater than 1.0. When it falls below 1.0, the position becomes eligible for liquidation. * For oracle implementation details, see the [dedicated oracle section](/learn/concepts/oracle/) * WAD represents a common scaling factor in DeFi of 10^18 used for representing decimal numbers in integer arithmetic Let's walk through a concrete example of calculating LTV and Health Factor for a position on Morpho: **Given values (with assumptions):** * Borrowed amount: 150,000 USDC (150,000,000,000 base units with 6 decimals) * Collateral amount: 2 cbBTC (200,000,000 base units with 8 decimals) * Oracle price: 1 × 10^39 (means 1 cbBTC = 100000 USDC). This is supposedly the value returned by the price function in the oracle used in the related market * Oracle price scale: 10^36 * LLTV: 86% (expressed as 0.86 × 10^18 or 860,000,000,000,000,000 in WAD units) ### Calculate the collateral value in loan token units [#calculate-the-collateral-value-in-loan-token-units] ```typescript // All calculations use BigInt (suffixed with 'n') to handle large numbers precisely const collateralValueInLoanToken = (collateralAmount * oraclePrice) / ORACLE_PRICE_SCALE; // = (200,000,000n * 1,000,000,000,000,000,000,000,000,000,000,000,000,000n) / 10n**36n // = 200,000,000,000 base units of loan token (USDC) ``` ### Calculate the current LTV [#calculate-the-current-ltv] ```typescript // Constants const WAD = 10n ** 18n; // Standard scaling factor (10^18) // Example values const borrowedAmount = 150_000_000_000n; // 150 billion units (e.g., USDC with 6 decimals) const collateralValueInLoanToken = 200_000_000_000n; // 200 billion units /** * Current LTV Calculation * ---------------------- * * Step 1: Calculate the raw LTV ratio, preserving precision with WAD */ const currentLTV = (borrowedAmount * WAD) / collateralValueInLoanToken; // = (150,000,000,000n * 10n**18n) / 200,000,000,000n // = 750,000,000,000,000,000n (scaled by WAD, representing 0.75 or 75%) /** * Option 1: Theoretical Formula * ---------------------------- * In theory, the conversion to percentage is simply: */ // Theoretical formula (doesn't work directly with BigInt) // currentLTVPercentage = (currentLTV / WAD) * 100 // Implementation of theoretical formula (using Number conversion) const theoreticalLTVPercentage = (Number(currentLTV) / Number(WAD)) * 100; // = 75.0000% /** * Option 2: Practical Display Implementation * ---------------------------------------- * For precise display with 4 decimal places, we: * 1. Scale up by the display factor (percentage * decimal precision) * 2. Divide by WAD to normalize * 3. Convert to Number and adjust for decimal places */ // Scale factor = 100 (for percentage) * 10000 (for 4 decimal places) = 1,000,000 const displayScaleFactor = 1_000_000n; const currentLTVPercentageScaled = (currentLTV * displayScaleFactor) / WAD; // = 750_000n (represents 75.0000%) // Convert the scaled BigInt result to a human-readable number const displayLTVPercentage = Number(currentLTVPercentageScaled) / 10000; // = 75.0000% (when displayed with 4 decimal places) ``` ### Calculate the health factor [#calculate-the-health-factor] ```typescript // Since LLTV is stored as a WAD, we need to account for scaling const healthFactor = (collateralValueInLoanToken * lltv) / borrowedAmount; // = (200,000,000,000,000n * 860,000,000,000,000,000n) / 150,000,000,000n // = 1,146,666,666,666,666,666 (scaled by WAD) // Convert to decimal const healthFactorDisplay = Number(healthFactor) / Number(WAD); // = 1.1467 (when converted to a human-readable decimal) ``` Since the Health Factor is greater than 1.0 (1.1467), this position is healthy and has a safety margin before liquidation could occur. #### Summary of Position: [#summary-of-position] * Current LTV: 75.00% * Max LTV (LLTV): 86.00% * Health Factor: 1.1467 * Status: Healthy * Liquidation Buffer: 11.00% (difference between current LTV and max LTV) This shows a position with an LTV of 75%, which is below the LLTV threshold of 86%. The Health Factor of 1.1467 confirms that the position is healthy with a 11% safety margin before liquidation. * For oracle implementation details, see the [dedicated oracle section](/learn/concepts/oracle/) * For examples of LTV calculations in Solidity, refer to [this code](https://github.com/morpho-org/morpho-blue/blob/12b8a453643d5ef9d55abd88b9f8cfa866882aa5/src/Morpho.sol#L532-L536) ## Liquidation Mechanism [#liquidation-mechanism] Liquidation is the primary defense against borrower defaults and is built directly into the Morpho core contracts. ### When Is a Position Liquidatable? [#when-is-a-position-liquidatable] A position becomes liquidatable when its LTV exceeds the market's Liquidation Loan-To-Value (LLTV) which can happen due to: * Collateral value decreasing * Debt increasing due to accrued interest * A combination of both factors **Example** If a borrower provides $100 of collateral in a market with an LLTV of 86%: * The position is safe when borrowed value ≤ $86 * The position becomes liquidatable when borrowed value > $86 (LTV > 86%) ### How Liquidation Works [#how-liquidation-works] When a position becomes liquidatable, any external party (a liquidator) can repay part or all of the borrower's debt in exchange for an equivalent amount of collateral plus a liquidation bonus. #### Liquidation Incentive Factor (LIF) [#liquidation-incentive-factor-lif] The liquidation bonus is determined by the Liquidation Incentive Factor (LIF), which depends on the market's LLTV: $$ \text{LIF} = \min(M, \frac{1}{\beta*\text{LLTV}+(1-\beta)}), \text{ with } \beta = 0.3 \text{ and } M= 1.15. $$
LIF vs LLTV graph

Liquidation Incentive Factor in relation to the Liquidation Loan-To-Value

* The entire LIF goes to the liquidator; Morpho protocol doesn't take a fee * For an 86% LLTV market, the LIF is approximately 1.05 (5% bonus) ### Liquidation Step-by-Step [#liquidation-step-by-step] ### Initial Position [#initial-position] A borrower deposits $100k of collateral in a market with LLTV = 86% and borrows $70k. Due to accrued interest (an example could be provided with only collateral price depreciation), the loan value increases to $86.01k. * Position status: LTV = 86.01% > LLTV (86%) → **Liquidatable** ### Liquidation Process [#liquidation-process] * Liquidator identifies the unhealthy position * Liquidator repays the $86.01k debt * LIF calculation: 1.05 (for 86% LLTV market) * Seized collateral: $86.01k × 1.05 = $90.31k ### After Liquidation [#after-liquidation] * Borrower: Debt cleared, retains $9.69k of collateral * Liquidator: Spent $86.01k, received $90.31k in collateral * Profit for liquidator: $4.3k minus gas fees and any swap costs ### Practical Consideration [#practical-consideration] For this liquidation to be profitable, the liquidator must ensure: $4.3k > (Gas fees + potential swap fees + potential price slippage costs) ### Key Features of Liquidation [#key-features-of-liquidation] * **Liquidation Amount**: Liquidators can repay up to 100% of the borrower's debt in a single transaction * **Trigger**: Only activates when LTV > LLTV * **Incentive**: Fixed LIF based on the market's LLTV * **Borrower Impact**: Potentially significant one-time liquidation * **Implementation**: Built into the Morpho core contract ## Bad Debt Consideration [#bad-debt-consideration] ### Morpho Vaults V2 - Loss Socialization [#morpho-vaults-v2---loss-socialization] Morpho Vault V2 implements an automatic loss socialization mechanism where losses - including bad debt from underlying adapters - are detected and distributed proportionally across all shareholders through share price depreciation. #### How It Works [#how-it-works] 1. **Loss Detection:** Each adapter reports the actual current value of its investments via a `realAssets()` function. When bad debt occurs in an underlying adapter, this reported value automatically decreases. 2. **Loss Realization:** During interest accrual, the vault compares the reported real asset value to its internally tracked total. If `realAssets` has dropped below the previously recorded `totalAssets`, the vault updates its accounting downward to reflect the loss. 3. **Automatic Socialization:** Share value is determined via `convertToAssets()`, which computes `shares × (totalAssets + 1) / (totalSupply + virtualShares)`. When `totalAssets` decreases, every share redeems for fewer assets. No shares are burned - all shareholders absorb the loss proportionally. #### Important Considerations [#important-considerations] * **Management fee** continue to accrue even during loss periods. * **Flash loan protection:** The vault includes safeguards against share price manipulation around loss realization events, as losses are only accounted for once per transaction. **Legacy Morpho Vault V1.1 caveat:** Morpho Vaults V1 are now legacy. However, Morpho Vault V2 deployments that still use a `MorphoVaultV1Adapter` to allocate into a legacy Morpho Vault V1.1 should be aware that Morpho Vaults V1.1 do not realize bad debt internally. As a result, the corresponding losses from that allocation will not be reflected in the V2 vault's share price until the V1.1 vault itself accounts for them. ### Legacy - Morpho Vaults V1 [#legacy---morpho-vaults-v1] Morpho Vault V1 has different mechanisms for accounting for bad debts depending on the vault version. #### Morpho Vaults V1.0 - Bad Debt Realization (Legacy) [#morpho-vaults-v10---bad-debt-realization-legacy] The Morpho Vaults created with [the MetaMorpho FactoryV1.0](https://github.com/morpho-org/metamorpho) have a mechanism to account for and realize bad debt in the event it arises. Typically, in other lending pool designs, accrued bad debt remains in the market forever until manual intervention to pay down the bad debt. If small enough, the markets can continue functioning. If it is significant, the market becomes unusable. Morpho Vaults V1.0 treat bad debt differently. When a liquidation leaves an account with some remaining debt, and without collateral to cover it, the loss is realized and shared proportionally between all lenders. As bad debt is realized at the time it occurs, a market can be used in perpetuity. #### Morpho Vaults V1.1 - No Bad Debt Realization (Legacy) [#morpho-vaults-v11---no-bad-debt-realization-legacy] The Morpho Vaults created with [the MetaMorpho Factory V1.1](https://github.com/morpho-org/metamorpho-v1.1) have a different mechanism as they do not realize bad debt and behave as other lending pools with accrued debt remaining in the market forever until manual intervention to pay down the bad debt. ## Liquidation FAQ [#liquidation-faq] #### At what price are liquidations done? [#at-what-price-are-liquidations-done] Liquidations occur at the current oracle price when a position's LTV exceeds the market's LLTV. The oracle price determines both when a position becomes liquidatable and the exchange rate during the liquidation process. The liquidator receives collateral valued at debt amount × LIF according to this oracle price, regardless of market conditions or price volatility elsewhere. #### What happens if the collateral price continues to fall? [#what-happens-if-the-collateral-price-continues-to-fall] If the collateral price continues to fall after a position becomes liquidatable: * The position becomes increasingly attractive for liquidators as they can seize a larger percentage of the collateral relative to its real market value * Liquidators are incentivized to act quickly to capture this value * The protocol's design encourages prompt liquidations to minimize systemic risk * There is no price floor - the position remains liquidatable until someone repays the debt #### What happens if the price recovers before liquidation happens? [#what-happens-if-the-price-recovers-before-liquidation-happens] If the collateral price recovers sufficiently before liquidation occurs: * The position's LTV may drop below the LLTV threshold * The position will no longer be liquidatable * The borrower retains their full collateral and remains responsible for their debt * This scenario highlights why borrowers should maintain a safety buffer below the LLTV #### How do competing liquidators interact? Is it first-come-first-served? [#how-do-competing-liquidators-interact-is-it-first-come-first-served] Yes, liquidations operate on a first-come-first-served basis: * The first transaction that successfully executes the liquidation claims the opportunity * There is no auction or bidding mechanism * In competitive environments, this can lead to MEV (Maximal Extractable Value) opportunities and potential gas price wars * Advanced liquidators may use flashbots or other private transaction methods to secure their liquidation #### Is there partial liquidation? [#is-there-partial-liquidation] Yes. Liquidators can choose to liquidate any amount of the borrower's debt up to the full amount. This allows for optimizing gas costs against liquidation profits, especially for large positions where full liquidation might cause significant market impact. #### How are liquidations guaranteed for assets like cbBTC/stETH? [#how-are-liquidations-guaranteed-for-assets-like-cbbtcsteth] Liquidations in Morpho are driven by economic incentives rather than absolute guarantees. The protocol doesn't perform liquidations itself; instead, when a position becomes unhealthy, it becomes eligible for liquidation by external actors. The system is designed with incentives (via the Liquidation Incentive Factor) to make liquidations profitable enough that third parties will consistently execute them. For assets like cbBTC or stETH, the key factor is whether the market's specific LIF provides enough incentive to outweigh potential costs associated with liquidating that particular collateral type. The ability to perform partial liquidations also helps with larger positions, as they can be liquidated incrementally by multiple parties or in several steps. #### Who are the liquidators and what tools do they use? [#who-are-the-liquidators-and-what-tools-do-they-use] Liquidators are permissionless participants in the Morpho ecosystem. Anyone who identifies an eligible position and has the necessary assets can perform a liquidation. The ecosystem includes a diverse range of participants, from sophisticated teams running automated bots to individuals. At a basic level, performing a liquidation only requires access to an RPC node and a wallet with sufficient assets. However, competitive liquidators typically utilize automated bots that continuously monitor onchain data (loan positions, oracle prices) to identify and execute liquidations efficiently. Some liquidators implement swap systems allowing them to seize collateral and repay loans without needing to hold the loan asset beforehand, effectively acting as a liquidity venue. The Morpho ecosystem also includes open-source liquidation bots that community members can deploy. ## Liquidation Tools [#liquidation-tools] Some community members have contributed and provided liquidation bots that could be deployed to liquidate positions. * Morpho Association nor authors of these repositories can be held responsible for any losses or damages that may result from the use of this information. * Users are advised to conduct their own research and exercise caution when applying any strategies or methods described herein. If you are comfortable with these conditions, you can explore the liquidation community section [here](/developers/ecosystem/liquidation-bots/) *** By understanding liquidation mechanics, users can better manage their risk on Morpho and protect their positions from adverse market movements. --- # Oracle Source: https://docs.morpho.org/learn/concepts/oracle
## What is an Oracle? [#what-is-an-oracle] Oracles are smart contracts that provide external data, particularly price information, to blockchain applications. In lending protocols like Morpho, oracles provide price data needed to determine what one token is worth relative to another. For example, the oracle of a market is supposedly answering the question: "How many USDC is 1 BTC worth right now?” ## Oracles in Lending Markets [#oracles-in-lending-markets] Traditional lending protocols rely on oracles to: * Determine the value of collateral assets * Calculate borrowing capacity * Trigger liquidations when positions become undercollateralized * Enable accurate interest rate calculations ## Oracle Implementation in Morpho [#oracle-implementation-in-morpho] Morpho takes an **oracle-agnostic approach**, allowing market creators to select the most appropriate price feed mechanisms based on specific market requirements. Each Morpho market specifies its oracle in the market parameters, ensuring that oracle implementations can be tailored to specific asset pairs. All oracles used in Morpho markets implement the `IOracle` [interface](/get-started/resources/contracts/oracles#price), which has a single, standardized function: ```solidity function price() external view returns (uint256); ``` This function returns the price of 1 unit of collateral token quoted in the loan token, with appropriate scaling to account for decimal differences between tokens. ## Types of Oracles Compatible with Morpho [#types-of-oracles-compatible-with-morpho] Various oracle implementations can be used with Morpho markets: 1. **Price Feed Oracles**: Utilize external price feeds (like Chainlink, Redstone, API3, Pyth, Chronicle) to calculate asset exchange rates. 2. **Exchange Rate Oracles**: Specialized for wrapped tokens or rebasing tokens where the exchange rate is deterministic (like wstETH/stETH). 3. **Fixed-Price Oracles**: Used for assets with known or predefined exchange rates, such as stablecoins pegged to the same value. ## MorphoChainlinkOracleV2: A Reference Implementation [#morphochainlinkoraclev2-a-reference-implementation] One reference implementation available is `MorphoChainlinkOracleV2`, which leverages Chainlink-compliant price feeds while supporting multiple routing configurations: * Direct feeds (e.g., stETH/ETH) * Inverse feeds (e.g., ETH/USDC → USDC/ETH) * Multiple feed routes (e.g., stETH/USD and USDC/USD) * Complex routing with multiple hops This implementation demonstrates the flexibility of the oracle system within Morpho, allowing markets to maintain accurate pricing even when direct price feeds aren't available. ## Deploy an Oracle [#deploy-an-oracle] Please refer to this section of the documentation to deploy a new oracle: [Deploy an Oracle](/curate/tutorials-market-v1/deploying-oracle/) ## Key Oracle Characteristics in Morpho Markets [#key-oracle-characteristics-in-morpho-markets] * **Purpose-Built**: Each oracle returns the specific exchange rate between a collateral asset and a loan asset * **Immutable**: Once a market is deployed, its oracle address cannot be modified * **Independent**: Each oracle operates autonomously and can use different pricing sources * **Flexible Implementation**: Curators can leverage various data sources while maintaining a consistent interface ## Oracle Selection by Market Curators [#oracle-selection-by-market-curators] Market curators (not Morpho) are responsible for selecting and implementing appropriate oracles for their markets. Each Morpho market specifies its oracle in the market parameters: `CollateralAsset/LoanAsset (LLTV%, OracleAddress, IRMAddress)` ## Oracle Security Considerations [#oracle-security-considerations] The security of an oracle is critical to the safety of a Morpho Market. Users should: * Verify the oracle implementation for any market they interact with * Understand the price sources being used * Consider potential manipulation vectors or failure modes The immutable nature of Morpho Markets means oracle selection is a permanent decision that defines the market's [risk profile](/learn/resources/risks/) ## Oracle community section [#oracle-community-section] Some community members contributed to adapters that could be plugged into oracles. * Morpho Association nor author of the repository cannot be held responsible for any losses or damages that may result from the use of this information. * Users are advised to conduct their own research and exercise caution when applying any strategies or methods described herein. If you are fine with it, jump on the liquidation community section [here](/developers/ecosystem/oracles/) --- # Public Allocator How Morpho vaults provide on-demand liquidity to isolated markets. Source: https://docs.morpho.org/learn/concepts/public-allocator Morpho markets isolate risk. That isolation also separates liquidity: one market can run short while a vault has assets elsewhere, including idle assets held directly by the vault. The Public Allocator connects that liquidity without merging the markets. Anyone can request a reallocation, but addresses with the vault's allocator role set the boundaries. The borrower gets deeper liquidity while each market keeps its own collateral, oracle, and risk profile. ## Why it exists [#why-it-exists] Suppose a borrower needs 1,000 USDC from one market, but only 600 USDC is available there. A vault that supplies the market may hold the missing 400 USDC in another market or as idle assets. The Public Allocator can move that 400 USDC into the target market before the borrow. An application can combine both actions, so the user submits one transaction. The caller never takes custody of the vault's assets. Without the Public Allocator, the borrower must reduce the loan, wait for liquidity, or split the position across markets. ## Vault V2 Public Allocator [#vault-v2-public-allocator] ## Vault controls [#vault-controls] The curator sets the vault's strategy and caps, then appoints addresses to the allocator role. An address with that role configures the Public Allocator boundaries: | Control | What it decides | | --------------------- | ------------------------------------------------------------------ | | Active adapter | Which supported Morpho market connections can participate | | Pull from idle assets | Whether the allocator can deploy assets held directly by the vault | | Pull from market | Which adapter and market pairs can provide assets | | Target ceiling | The maximum resulting allocation for an adapter and market pair | | Penalty | The percentage of moved assets that the caller pays to the vault | All existing Vault V2 caps remain binding. The Public Allocator can use available room inside those limits, but it cannot expand them or change the vault's strategy. The Public Allocator penalty is separate from any in-kind redemption penalty configured by the vault. ## What users gain [#what-users-gain] * Larger borrows and loan-asset withdrawals can use liquidity held elsewhere by participating vaults. * One bundled transaction can reallocate assets and complete the user's action together or not at all. * Markets keep isolated risk even when applications present their liquidity as one deeper source. * Vault depositors receive any configured penalty when their liquidity is moved. ## When liquidity is unavailable [#when-liquidity-is-unavailable] The Public Allocator does not guarantee every request. A reallocation can be limited by its settings, the vault's caps, available idle assets, or liquidity in source markets. Another transaction can also change the available amount before execution. Applications should show only current, eligible liquidity and rebuild the transaction from fresh data before submission. Follow [Find and use Vault V2 Public Allocator liquidity](/developers/borrow/tutorials/public-allocator/) for the SDK algorithm, data-loading options, and transaction flow.
--- # Rewards on Morpho Source: https://docs.morpho.org/learn/concepts/rewards
## What are Rewards? [#what-are-rewards] Rewards are incentives distributed to Morpho users to encourage specific behaviors like supplying assets, borrowing, or providing collateral. These rewards typically come in the form of tokens (like MORPHO or other assets) and are provided by: * The Morpho governance * Market creators bootstrapping liquidity * Vault curators attracting depositors * Token issuers promoting asset usage ## Types of Rewards [#types-of-rewards] ### Vault Rewards [#vault-rewards] Vault depositors can earn rewards from two sources: * **Vault Campaigns**: Direct rewards for depositing into a specific vault * **Market Campaigns**: Rewards automatically forwarded from underlying markets where the vault allocates This dual-source structure allows vault depositors to benefit from multiple incentive programs simultaneously. ### Market Rewards [#market-rewards] Market participants can earn rewards based on their activity: * **Supply Rewards**: For supplying assets to a market * **Borrow Rewards**: For borrowing from a market It is also possible to incentivize a simple collateral deposit (without the borrow action required). Borrowers can earn rewards on their borrow position, potentially offsetting or exceeding their borrowing costs. ## How It Works [#how-it-works] 1. **Earning**: Rewards accrue automatically while you participate in incentivized vaults or markets 2. **Computation**: Rewards are calculated offchain based on your onchain activity 3. **Distribution**: Rewards become claimable through Merkl 4. **Claiming**: Claim your earned rewards before the applicable claim deadline ## Distribution System [#distribution-system] All Morpho rewards are distributed through **Merkl**, which updates every 8 hours and handles all active programs. If you have unclaimed rewards from historical Morpho programs (pre-Merkl migration), claim them via [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). Users simply need to participate in the protocol to earn rewards. No special actions are required beyond claiming before the applicable deadline. For Merkl campaigns distributed by Morpho governance, unclaimed rewards are reallocated 6 months after the campaign ends. Once reallocated, these rewards are no longer claimable. ## Learn More [#learn-more] **For Users:** * Rewards accumulate automatically while you use Morpho * Claim rewards through the Morpho interface before the applicable deadline * Tutorial: [How to Claim Rewards](https://help.morpho.org/en/articles/12032660-rewards-on-the-morpho-app) **For Developers:** * Complete integration guide: [Rewards Integration](/developers/rewards/get-started) * Technical concepts: [Reward Campaigns](/developers/rewards/concepts/reward-campaigns) * Distribution details: [Distribution System](/developers/rewards/concepts/distribution-system) --- # Morpho Vault V2 Source: https://docs.morpho.org/learn/concepts/vault-v2
**Morpho Vault V2** is Morpho's permissionless vault framework for curated lending strategies. It is designed around **Adapters**, a granular **ID & Cap System**, and automatic interest accrual through **real-time asset reporting**. Morpho Vault V2 is the primary vault surface documented in the Learn section. It builds on the core variable yield generation model while extending it through adapters, granular IDs and caps, and real-time asset reporting. If you need legacy Vault V1 reference material, use the archived curator docs in [Curate](/curate/tutorials-v1/vault-creation/). ## Why Vault V2 Matters [#why-vault-v2-matters] Morpho Vault V2 is designed for flexibility and control: * **Universal yield routing**: Curators can enable approved adapters rather than hard-coding a single yield source. * **Automatic aggregation**: Adapters report current assets through `realAssets()`, allowing the vault to aggregate value across positions in real time. * **Granular risk controls**: Curators can set both absolute and relative caps on abstract risk identifiers such as collateral, oracle, or protocol exposures. * **Role separation**: Owner, Curator, Allocator, and Sentinel responsibilities are clearly separated for stronger operational security. * **Built-in gating and liquidity controls**: Vault V2 supports optional gates, idle assets, liquidity adapters, and in-kind redemption flows. ## Core Concepts of Morpho Vault V2 [#core-concepts-of-morpho-vault-v2] ### 1. The Adapter Model: A Universal Gateway to Yield [#1-the-adapter-model-a-universal-gateway-to-yield] The central innovation of Morpho Vault V2 is the **Adapter** system. An adapter is a smart contract that acts as a translator, containing the logic to interact with a specific protocol and automatically report the current value of allocated assets. * **How it works**: The `Allocator` calls `allocate` on the vault, specifying a target adapter, an amount, and any protocol-specific data. The vault then transfers assets to the adapter, which executes the supply logic on the selected yield source, including underlying Morpho variable rate markets and future Morpho markets. * **Automatic Asset Reporting**: Each adapter implements a `realAssets()` function that returns the current value of all allocated assets it manages. This allows the vault to automatically calculate total assets by aggregating across all adapters. * **Future-Proof**: New adapters can be developed and enabled by the `Curator` at any time, allowing the vault to integrate with new sources of onchain yield without requiring an upgrade to the vault itself. Current example includes `MorphoMarketV1AdapterV2`. ### 2. Granular Risk Curation: The ID & Cap System [#2-granular-risk-curation-the-id--cap-system] Morpho Vault V2 introduces a far more sophisticated system for curating risk. Instead of a single cap per market, Curators can now define and cap risk based on abstract identifiers (`ids`), where each `id` represents a common risk factor. For example, the `MorphoMarketV1AdapterV2` can generate `ids` for: * A specific collateral asset (e.g., `keccak256("collateralToken", stETH_address)`) * A specific market configuration (e.g., `keccak256("collateralToken/oracle/lltv", ...)`). The `Curator` can then set both **absolute caps** (a fixed asset amount) and **relative caps** (a percentage of the vault's total assets) on these `ids`. This allows for multi-dimensional risk policies, such as: * Max total exposure to `stETH` as collateral: 15M (absolute cap on the collateral `id`). * Max 20% of the vault allocated to any market using a specific, new oracle (relative cap). ### 3. Advanced Liquidity Curation [#3-advanced-liquidity-curation] Morpho Vault V2 separates idle liquidity from allocated capital more explicitly. * **Idle Assets**: All user deposits and withdrawals flow through the main vault contract, which holds the idle assets. * **Liquidity Adapter**: The `Allocator` can designate a specific, highly liquid adapter as the `liquidityAdapter`. If idle assets are insufficient to cover a withdrawal, the vault will automatically pull assets from this market. This adapter also receives all new deposits, ensuring assets are immediately put to work. * **In-Kind Redemptions (`forceDeallocate`)**: As a final noncustodial guarantee, users can `forceDeallocate` assets from any adapter back to the vault's idle pool. By combining this with a flash loan, a user can always exit the vault by exchanging their vault receipt tokens for a direct position in an underlying protocol, even if the vault is illiquid. A small penalty applies to disincentivize misuse of this emergency function. Some gate implementations can affect noncustodiality guarantees by preventing certain users from depositing to or withdrawing from the vault. ## Advanced Role System [#advanced-role-system] Morpho Vault V2 refines the role system for greater security and flexibility: * **Owner**: Controls top-level permissions. Can appoint the Curator and Sentinels. Has no direct control over assets or risk parameters. * **Curator**: The risk configuration role. Configures adapters, caps, fees, and interest rate limits. Most actions are timelocked. * **Allocator**: The active execution role. Allocates assets to enabled adapters and configures the `liquidityAdapter`. * **Sentinel**: The safety-focused role that can reactively reduce risk by deallocating assets, decreasing caps, or revoking pending timelocked actions. ## Other Key Features [#other-key-features] * **New Fee Structure**: Supports both a **performance fee** (up to 50% on yield) and a **management fee** (up to 5% on total assets). * **Interest Rate Controls**: The Allocator can set a `maxRate` that caps how quickly `totalAssets` can grow, useful for implementing capped rate distributions. * **Gating & Compliance**: Optional `Gate` contracts can be set by the Curator to enforce onchain rules for deposits, withdrawals, and receipt token transfers (e.g., for KYC/whitelisting). ### Fee Wrappers [#fee-wrappers] A **Fee Wrapper** is a specialized use of Vault V2: a lightweight vault configured to wrap a single existing Morpho Vault V2, adding a fee layer for distribution channels. The adapter configuration is permanently locked at deployment, making the wrapper immutably bound to its child vault. Fee Wrappers enable B2B distribution, per-customer vaults, and white-label products with configurable performance and management fees. For detailed documentation on Fee Wrapper architecture, deployment, and integration, see the [Fee Wrapper concept page](/developers/earn/concepts/fee-wrapper). ## Is Morpho Vault V2 Non-Custodial? [#is-morpho-vault-v2-non-custodial] **Yes, Morpho Vault V2 maintains noncustodial guarantees through three fundamental mechanisms: in-kind redemptions, timelocks, and role-based access control.** Some [gate](#gates-optional-access-control) implementations can affect noncustodiality guarantees by preventing certain users from depositing to or withdrawing from the vault. ### Core Non-Custodial Mechanisms [#core-non-custodial-mechanisms] #### 1. In-Kind Redemptions with `forceDeallocate` [#1-in-kind-redemptions-with-forcedeallocate] In-kind redemption in Morpho Vault V2 transfers a user’s position from the vault itself to one of its underlying adapters. It does not withdraw assets out of the Morpho protocol; instead, it shifts the user’s exposure from the vault to the underlying positions, even when those positions are illiquid. This transition happens through the permissionless `forceDeallocate` function. This mechanism enables: * **Guaranteed exits**: Users can redeem their Morpho Vault V2 receipt tokens directly for underlying positions in protocols, similar to ETF redemptions * **Permissionless operation**: Anyone can call `forceDeallocate` to move assets from adapters back to the vault's idle assets * **Flashloan-enabled exits**: Users can flashloan liquidity, supply it to an adapter's market, then withdraw through `forceDeallocate` before repaying the flashloan **Complete In-Kind Redemption Workflow:** When a vault lacks sufficient idle liquidity for withdrawals, users can perform an in-kind redemption through flashloan-enabled exits: 1. **User flashloans the required liquidity amount** 2. **User deposits flashloaned assets directly into the underlying market/protocol** 3. **User calls `forceDeallocate` to withdraw equivalent assets from the adapter** 4. **User withdraws the deallocated assets from the vault** 5. **User repays the flashloan with the withdrawn assets** 6. **User retains their position in the underlying market as compensation** A small penalty (up to 2%) may apply to discourage manipulation, but this doesn't prevent exits-it only adds a minor cost. This mechanism ensures that even if a vault's underlying markets become illiquid, users are never permanently locked in it. They can always exit by effectively swapping their vault receipt tokens for direct positions in the underlying protocols. #### 2. Comprehensive Timelock System [#2-comprehensive-timelock-system] All potentially harmful curator actions are protected by configurable timelocks (0 to 3 weeks), giving users time to react: **Timelocked actions include:** * Increasing allocation caps (absolute and relative) * Enabling new adapters or allocators * Setting performance/management fees (capped at 50% and 5% respectively) * Configuring access gates * Modifying fee recipients **Timelock properties:** * Users can monitor pending changes via the `submit` function, which records proposal timestamps in the `executableAt` mapping * Sentinels can revoke malicious proposals before execution * Emergency functions like decreasing caps can be called immediately by curators or sentinels for protective risk management * Some timelocks can be made infinite through `abdicate`, permanently preventing certain actions **Key protection:** The timelock system ensures that users always have advance notice of changes that could affect their assets, with emergency overrides only available for actions that reduce risk exposure (like lowering caps), never for actions that could increase risk. #### 3. Role Separation and Limitations [#3-role-separation-and-limitations] Morpho Vault V2's role-based architecture ensures no single party can unilaterally harm users through strict separation of concerns, ensuring that even if one role is compromised, user assets remain protected by the constraints and oversight of other roles, combined with timelock protection for any potentially harmful changes. Check the roles declination on the related [section](/curate/concepts/roles/#morpho-vaults-v2-roles). ### Additional Safety Mechanisms [#additional-safety-mechanisms] #### Caps and Risk Controls [#caps-and-risk-controls] * **Absolute caps**: Hard limits on allocation to specific protocols/markets * **Relative caps**: Percentage-based limits relative to total vault assets * **Id system**: Groups related risks (e.g., same collateral) under unified caps #### Gates (Optional Access Control) [#gates-optional-access-control] When enabled, gates control: * Receipt token transfers (who can send/receive vault receipt tokens) * Asset deposits (who can deposit into vault) * Asset withdrawals (who can receive assets from vault) Gates are timelocked and can be permanently disabled through abdication. ### Adapter Registry [#adapter-registry] **Morpho Vaults V2** use the **Morpho Registry**, the official adapter registry maintained by Morpho governance. This registry defines which adapters are approved for use, ensuring that deposits stay within Morpho-reviewed protocols. When the curator locks (abdicates) the registry, it can never be changed-giving users a strong, permanent guarantee about where their assets can be allocated. Morpho Vaults V2 benefit from the full support of Morpho's infrastructure: * **Full app integration**: Listed in the [Morpho app](https://app.morpho.org) (subject to listing process). * **Curation tools**: Configurable through the [Curator app](https://curator.morpho.org). * **Data utilities**: [Morpho GraphQL API](https://api.morpho.org/graphql), [Dune Analytics Dashboards](https://dune.com/morpho). * [Security audits](/get-started/resources/audits/) for all supported contracts and flows. * Thorough product and technical documentation. * Peripheral utilities like the [Oracle tester and decoder](https://oracles.morpho.dev/). #### Immutable Contracts [#immutable-contracts] All vault contracts are immutable-no upgrades can change the core noncustodial guarantees after deployment. ### Why This Matters [#why-this-matters] These mechanisms ensure that: 1. **No entity can permanently lock user assets** (guaranteed via `forceDeallocate`) 2. **No surprise changes can harm users** (guaranteed via timelocks) 3. **No single party controls user assets** (guaranteed via role separation) The combination of guaranteed exits, transparent timelocks, and distributed control makes Morpho Vaults V2 genuinely noncustodial-users retain ultimate control over their assets even when delegating configuration to curators and allocators. --- # Morpho Governance Source: https://docs.morpho.org/learn/governance --- # The MORPHO token Source: https://docs.morpho.org/learn/governance/morpho-token MORPHO is the Morpho Protocol's governance token. The governance system uses a weighted voting system in which the number of MORPHO tokens held determines voting power. MORPHO holders can vote on changes or improvements to the protocol, see [this section](/learn/governance/organization/#morpho-dao-governance) to get the exhaustive list of actions. ## Legacy and Wrapped MORPHO [#legacy-and-wrapped-morpho] The original, now legacy, MORPHO token was deployed as an immutable contract and lacked functionality associated with onchain vote accounting. The governance voted to create a contract to wrap the legacy tokens into wrapped MORPHO to enable onchain vote tracking functionality in [MIP-75](https://snapshot.org/#/morpho.eth/proposal/0x53c5f01dac396b97650f37fccfa0910457b27a1fc06c5ec5d54ee0e6e5408180). In addition to onchain vote tracking, using an upgradeable token makes it possible to conform to a crosschain interoperability standard in the future, minimizing friction for MORPHO holders who want to move their tokens between chains. Although legacy MORPHO tokens can be freely converted to wrapped MORPHO 1:1 via the wrapper contract, there could be a risk that legacy tokens might be used mistakenly in external integrations, such as exchanges. To prevent this, only the wrapped MORPHO will be transferable. Users can easily wrap their existing legacy MORPHO tokens on the Morpho App. ## MORPHO Token Addresses [#morpho-token-addresses] You can find MORPHO token contracts, related contracts addresses as well as Github repositories in the [addresses section](/get-started/resources/addresses/#morpho-token). The MORPHO token has a maximum supply of 1,000,000,000 MORPHO. ## Token Distribution & Vesting [#token-distribution--vesting] The overall distribution of MORPHO tokens, including vested and unvested allocations, as of 7 November 2024.
### Morpho governance [#morpho-governance] 35.4% of MORPHO tokens are owned and controlled by the Morpho governance. Holders of the MORPHO token can vote on how these tokens are used. ### Users & Launch Pools [#users--launch-pools] 4.9% of MORPHO tokens have been distributed from the Morpho governance to users of the Morpho Protocol and launch pool participants. The Morpho governance continues to distribute MORPHO token as rewards. ### Morpho Association [#morpho-association] 6.3% of MORPHO tokens are allocated to the Morpho Association for ecosystem development. This allocation can be used to fund partnerships, contributors, and any other initiatives that help grow the Morpho Protocol and advance Morpho’s network. ### Reserve for Contributors [#reserve-for-contributors] 5.8% of MORPHO tokens are reserved for contributors to the Morpho Protocol for their role in the development and growth of the network. This reserve includes unallocated tokens [set aside by the Morpho governance](https://forum.morpho.org/t/mip1-early-core-contributors-allocation/94) for future contributors to the Morpho Protocol such as Morpho Association employees, service providers, contractors, and research institutes. ### Strategic Partners [#strategic-partners] 27.5% of MORPHO tokens have been allocated to Strategic Partners tokens in exchange for providing support - monetary or otherwise - to the Morpho Protocol. These MORPHO tokens are distributed according to three vesting schedules based on the time these Strategic Partners joined the Morpho ecosystem. **Cohort 1**: 4.0% allocated over a 3 year vest, with a 6 month lockup from when the MORPHO token contract was deployed on 24 June 2022. **Cohort 2**: 16.8% originally allocated over a 3 year vest, with a 6 month lockup from 24 June 2022. However, these strategic partners have agreed to relock to a 6 month linear vesting, following a 6 month lockup from 3 October 2024. This means 100% is vested by 3 October 2025 at the latest. **Cohort 3**: 6.7% allocated over 2 year linear vest, following a 1 year lockup from 21 November 2024. This means 100% is vested by 21 November 2027 at the latest. ### Founders [#founders] 15.2% of MORPHO tokens have been allocated to Morpho’s founders. The tokens were originally allocated over 3 year vest, with a 1-year lockup from when the token was deployed on 24 June 2022. However, all co-founders have agreed to relock to additional 2 year linear vest, following a 1 year lockup from the earliest of any future transferability date or May 17th 2025. This means 100% is vested by 17 May 2028 at the latest. ### Early Contributors [#early-contributors] 4.9% of MORPHO tokens have been allocated to early contributors, including Morpho Association's contributors, independent researchers, and advisors, in exchange for contributions to the Morpho Protocol. These tokens have been subject to either: * A 3-year vesting schedule with a 6-month lockup, or * A 4-year vesting schedule with a 4-month lockup. ## Circulating Supply [#circulating-supply] The circulating supply on transferability date is expected to be approximately 11.2%. The chart below shows how the circulating supply of MORPHO tokens might evolve over time:
*Note: the evolution of the MORPHO circulating supply over time is subject to change as the governance makes future decisions on use of reserves and rate of distributions.* ## Initial Non-Transferability Phase [#initial-non-transferability-phase] While token-based governance allows anyone to participate in governance of the Morpho network, many token launches are very unfair because the launch date & price are often a centralized decision. Moreover, there is usually a large information asymmetry between the initial team/investors and the potential buyers. The MORPHO token was launched as non-transferable to tackle those issues, allowing the governance to turn on transferability anytime. This enabled the protocol to reach meaningful traction prior to a decentralized token launch process. Under this vision, a good equilibrium for the control of a governance is a clear distribution between: 1. Users: To own a share of the network and to provide a positive feedback loop on the product. 2. Contributors: To keep improving and building based on the user's feedback and on their technical vision. 3. Strategic Partners: To provide capital and guidance to fuel those contributions. The current state of distributions in categories 2. and 3. are described above. As for 1., it is described in [the Rewards section](/learn/concepts/rewards/#uniform-rate-programs). ## Morpho governance Voted to Enable Token Transferability [#morpho-governance-voted-to-enable-token-transferability] Although initially deployed as a non-transferable token for the reasons mentioned above, the Morpho governance voted to enable the transferability of the MORPHO token to advance Morpho’s mission to make financial infrastructure a public good. Since launching in June 2022, the governance has progressively distributed ownership to the protocol’s users, strategic partners, risk curators, and other contributors aligned with Morpho's long-term vision. Now, by enabling token transferability, the Morpho governance is opening governance participation in the Morpho network to anyone. As discussed [in the community forum](https://forum.morpho.org/t/inviting-the-morpho-community-to-discuss-transferability/742), this move is a key step toward realizing the protocol’s mission of transforming financial infrastructure into a public good. It will empower a broader range of participants to engage with and contribute to the Morpho ecosystem, bringing new perspectives and ensuring that governance decisions reflect the interests of a more diverse community. MORPHO token transferability was enabled on November 21, 2024. ## FAQ [#faq] ### Why are there two MORPHO tokens? What’s the difference between them? [#why-are-there-two-morpho-tokens-whats-the-difference-between-them] The original, now legacy, MORPHO token was deployed as an immutable contract and lacked functionality associated with onchain vote accounting. The Morpho governance voted to create a contract to wrap the legacy tokens into wrapped MORPHO to enable onchain vote tracking functionality in [MIP-75](https://snapshot.box/#/s\:morpho.eth/proposal/0x53c5f01dac396b97650f37fccfa0910457b27a1fc06c5ec5d54ee0e6e5408180). In addition to onchain vote tracking, using an upgradeable token makes it possible to conform to a crosschain interoperability standard in the future, minimizing friction for MORPHO holders who want to move their tokens between chains. Although legacy MORPHO tokens can be freely converted to wrapped MORPHO 1:1 via [the wrapper contract](https://etherscan.io/address/0x9d03bb2092270648d7480049d0e58d2fcf0e5123), there could be a risk that legacy tokens might be used mistakenly in external integrations, such as exchanges. To prevent this, only the wrapped MORPHO is transferable. Users can easily wrap their existing legacy MORPHO tokens on the Morpho App (see How to wrap [here](/learn/governance/morpho-token/#how-to-wrap-the-legacy-morpho-token)). ### How to wrap the Legacy MORPHO token on the Morpho interface [#how-to-wrap-the-legacy-morpho-token-on-the-morpho-interface] If you hold legacy MORPHO, go to [app.morpho.org](https://app.morpho.org/?network=mainnet) and connect the wallet holding your tokens. A notification card will appear at the bottom left of the screen. Click `Wrap` in the card.
Clicking on the `Wrap` button will trigger a modal, showing your Legacy MORPHO token balance and proposing you to wrap it.
With `Use direct contract calls` off (the default), click `Wrap MORPHO`. Your wallet will first ask you to sign a permit, then submit the wrap transaction through the bundler flow. To use direct contract calls instead, enable `Use direct contract calls`. Your wallet will then ask you to submit two transactions: an approval followed by the wrap transaction. ### How to wrap the Legacy MORPHO token at contract level [#how-to-wrap-the-legacy-morpho-token-at-contract-level] If you hold Legacy MORPHO tokens and want to migrate them onchain by yourself without going through the Morpho app, you can directly interact at contract level, using Etherscan for instance. To do so, you will have to: * **Connect** your wallet to Etherscan. * **Approve** your Legacy MORPHO tokens to the wrapper. This can be done with [the legacy token contract's `approve` function](https://etherscan.io/address/0x9994E35Db50125E0DF82e4c2dde62496CE330999#writeContract#F1), (the arguments should be the wrapper contract’s address and your amount of legacy tokens). E.g: - Wrapper address: 0x9D03bb2092270648d7480049d0E58d2FcF0E5123 - 1000000000000000000 for 1 Legacy MORPHO Token (18 decimals)
* **Migrate** your tokens on the wrapper by calling [the wrapper contract’s `depositFor` function](https://etherscan.io/address/0x9d03bb2092270648d7480049d0e58d2fcf0e5123#writeContract#F1) (the arguments should be your address, or another address if you want another recipient for the migrated tokens, and your amount of legacy tokens).
Feel free to get in touch via the chatbox on **[help.morpho.org](https://help.morpho.org)**. --- # Morpho Governance Source: https://docs.morpho.org/learn/governance/organization Morpho Protocol governance is intentionally limited in scope. The core smart contract code is immutable. The powers of the governance include: * Control over the MORPHO tokens in the governance treasury * Ownership of the upgradeable MORPHO token contract * Activating and adjusting the fee switch(Link:[https://github.com/morpho-org/morpho-blue/blob/78a059bd7318418465b06b8da96dc133e31ed3d9/src/Morpho.sol#L122-L145](https://github.com/morpho-org/morpho-blue/blob/78a059bd7318418465b06b8da96dc133e31ed3d9/src/Morpho.sol#L122-L145)) (capped at a maximum of 25% of interest paid by borrowers) and setting a fee recipient address * Whitelisting new [LLTVs and IRMs](/learn/concepts/market/#dao-approved-lltv--irm) for use in the Morpho Protocol * Granting licenses of the Morpho Protocol code by updating the ENS record at morpho-blue-core-license-grants.morpho.eth * Ownership of the morpho.eth ENS domain and subdomains, which are used for clearly labelling relevant protocol addresses The governance discussions happen on [the Morpho governance forum](https://forum.morpho.org/). This discussions can lead to proposals that will be voted on. MORPHO token-holders can vote on the actions taken by the Morpho governance using the Snapshot interface at [snapshot.org/morpho.eth](https://snapshot.org/#/s\:morpho.eth). Anyone who holds or has delegated to them at least 500k MORPHO tokens can submit a proposal for voting. This threshold is adjustable by the governance. Once a proposal reaches the required majority, the outcome of the vote binds the collective body of MORPHO token-holders, regardless of how any individual token-holder voted or whether they participated in the vote. Governance decisions are taken in the interest of the Morpho network as a whole, and do not necessarily confer a direct or individual benefit on each token-holder taken separately. Once approved by a governance vote, actions are implemented by the [5/9 governance Multisig](/learn/governance/organization#multisig-and-addresses) whose members are decided by the Morpho governance. ## Morpho Association [#morpho-association] The Morpho Association is a French nonprofit entity whose mission is to grow Morpho's network effects through research, development and promotion of the Morpho protocol. In practice, the Morpho Association: * Supports the development, growth, security and adoption of the Morpho protocol * Hosts a front end to the Morpho protocol to facilitate the access of new users to Morpho: [morpho.org](http://morpho.org/) * Hosts technical documentation to facilitate the work of new Morpho developers * Holds the intellectual property of open-source codebases such as morpho-blue, metamorpho-v1.1 or vault-v2 and hosts it on the morpho-org GitHub under a GPL3 license. ## Multisig and Addresses [#multisig-and-addresses] | Name | Address/Details | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **Morpho Contracts** | See the [Addresses](/get-started/resources/addresses/#1) section | | **Morpho governance** (Ethereum) | morpho.eth (5/9 multisig): [0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa](https://etherscan.io/address/0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa) | | **Morpho governance** (Base) | morpho.eth (5/9 multisig): [0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa](https://basescan.org/address/0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa) | | **MORPHO Rewards Multisig** (Ethereum) | [0xF057afeEc22E220f47AD4220871364e9E828b2e9](https://etherscan.io/address/0xF057afeEc22E220f47AD4220871364e9E828b2e9) (3/5) | | **MORPHO Rewards Multisig** (Base) | [0x5Eb982bb1E620cC3927E5CF8A5D207e667643297](https://basescan.org/address/0x5Eb982bb1E620cC3927E5CF8A5D207e667643297) (3/5) | --- # Formal Verification Source: https://docs.morpho.org/learn/resources/formal-verification ## Formal Verification [#formal-verification] Morpho follows a multi-faceted approach to security. Morpho security practices include formal verification, mutation tests, fuzzing, unit testing, and peer reviews that can be found within the respective [GitHub repositories](https://github.com/morpho-org). External measures include professional security reviews, contests, and pre/post-deployment bounties. A whole article was dedicated to the [Morpho Security Framework](https://morpho.org/blog/morpho-blue-security-framework-building-the-most-secure-lending-protocol/). The full list of formal verifications is available below. | Formally Proven | Scope | Tool Used | | :---------------------------------------------------------------------------------------------------------------------- | :----------------------------------------- | :----------------------------------------------------------------------------- | | [Morpho token](https://github.com/morpho-org/morpho-token/tree/main/certora) | ERC20 and delegation logic | [Certora](https://www.certora.com/) | | [Morpho Blue](https://github.com/morpho-org/morpho-blue/tree/main/certora) | core logic | [Certora](https://www.certora.com/) & [Halmos](https://github.com/a16z/halmos) | | [Morpho Blue - Pre-liquidation](https://github.com/morpho-org/pre-liquidation/tree/main/certora) | core logic | [Certora](https://www.certora.com/) | | [Bundler3](https://github.com/morpho-org/bundler3) | core logic | [Certora](https://www.certora.com/) | | [Vault V2](https://github.com/morpho-org/vault-v2) | core logic | [Certora](https://www.certora.com/) | | [Midnight](https://github.com/morpho-org/midnight) | core logic | [Certora](https://www.certora.com/) | | [Morpho data structures (Legacy)](https://github.com/morpho-org/morpho-data-structures) | double linked list and logarithmic buckets | [Certora](https://www.certora.com/) & [Halmos](https://github.com/a16z/halmos) | | [Morpho Aave V3 (Legacy)](https://github.com/morpho-org/morpho-aave-v3) | core logic | [Why3](https://why3.org/) | | [Morpho optimizers (Legacy)](https://github.com/morpho-org/morpho-optimizers) | Merkle tree and claim function | [Certora](https://www.certora.com/) & custom checker | | [Universal rewards distributor (Legacy)](https://github.com/morpho-org/universal-rewards-distributor/tree/main/certora) | Merkle tree and claim function | [Certora](https://www.certora.com/) & custom checker | | [Morpho token (legacy)](https://github.com/morpho-org/semitransferable-token/tree/master/certora) | authorization system | [Certora](https://www.certora.com/) | | [Morpho utils (Legacy)](https://github.com/morpho-org/morpho-utils/tree/main/certora) | math functions | [Certora](https://www.certora.com/) | | [Vault V1.0 (Legacy)](https://github.com/morpho-org/metamorpho/tree/main/certora) | core logic | [Certora](https://www.certora.com/) | --- # Risk & Security Documentation Source: https://docs.morpho.org/learn/resources/risks By using Morpho or Morpho Vaults, you assume the risks associated. The following section provides an overview of different types of risks you should be aware of when using Morpho and Morpho Vaults. This overview is not exhaustive and may not cover all potential risks to which you might be exposed. Morpho is committed to use industry-leading security practices. Yet, there are still a number of risks associated with the use of Morpho and Morpho Vaults that users must be aware of. ## Morpho Security practices [#morpho-security-practices] Morpho is known for its industry-leading security practices and follows a multi-faceted approach to security. Morpho security practices include formal verification, mutation tests, fuzzing, unit testing, and peer reviews that can be found within respective [Github repositories](https://github.com/morpho-org). External measures include professional security reviews, contests, and pre/post-deployment bounties. A whole article was dedicated to the [Morpho Security Framework](https://morpho.org/blog/morpho-blue-security-framework-building-the-most-secure-lending-protocol/). The Morpho apps and smart contracts have been audited extensively by a wide range of security firms, with every new app and feature undergoing audits before release. The full list of audits is available in the [Audits](/get-started/resources/audits/) section. ## Smart Contract Risk [#smart-contract-risk] There is an inherent risk that the protocol could contain a smart contract vulnerability or bug. Several security measures are employed to mitigate this risk: * Core contracts are immutable * It is a simple and open-sourced [code base](/get-started/resources/contracts/) that avoids complexities * The code has been audited by multiple auditors, refer to the [security reviews](/get-started/resources/audits/) section * [Formal verification](/learn/resources/risks/#formal-verifications) has been applied using [Certora](https://www.certora.com/) * One ongoing bug bounty program: [Cantina - $2,500,000](https://cantina.xyz/bounties/35a5f0a1-2ffd-432c-8f3b-77d169add8c3) (Morpho Blue, Morpho Midnight & Morpho Vaults) ## Oracle Risk [#oracle-risk] Every Morpho market is connected to an oracle, established at market creation. It is important to understand that no oracle is immune to price manipulation, which can lead to liquidations or even bad debt. However, some oracles will be more resistant and resilient than others. When assessing the reliability of an oracle, consider factors such as safety and liveness, particularly if the oracle is centralized. Also, take into account the settings and processes pertaining to the definition and frequency of price updates. Markets with a faulty oracle can lead to loss of assets across Morpho markets and any vault strategy that depends on them. ## Counterparty Risk [#counterparty-risk] Before entering a market, it's crucial to conduct thorough due diligence on the loan asset and the collateral asset to understand who holds power over them. Factors to consider include centralization, as a centralized governance could blacklist a specific user or even Morpho, resulting in a loss of assets. The distribution of the asset is also important, as a high concentration can cause extreme price fluctuations. ## Liquidation Risk [#liquidation-risk] ### Liquidation Risk (for borrowers) [#liquidation-risk-for-borrowers] Each Morpho market is linked to an immutable Liquidation Loan-to-Value (LLTV). If the Loan-To-Value of your position exceeds this LLTV, you will face liquidation. When borrowing on Morpho, carefully select the market and diligently manage the health of your position. ### Bad Debt Risk (for lenders) [#bad-debt-risk-for-lenders] There could be circumstances in which the collateral's value for a position drops below the borrowed amount before liquidators can close the position. In such cases, the borrower holding this position has no incentive to repay the debt. Morpho has different mechanisms for accounting for bad debts. You can read more about it in the [bad debt section](/learn/concepts/liquidation/#bad-debt). ### Liquidity Risk (for lenders) [#liquidity-risk-for-lenders] Liquidity refers to the access to supplied assets. A lack of liquidity can prevent suppliers from withdrawing their assets for a certain period of time. Liquidity issues are tackled through the interest rate model. Before providing liquidity, it's essential to understand the market's interest rate model. This understanding will help you estimate the level of liquidity you can expect in that market. ## Morpho Vaults Specific Risks [#morpho-vaults-specific-risks] ### Vault Governance Risks [#vault-governance-risks] Key [roles](/curate/concepts/roles/) within a Morpho Vault V2 wield significant power and can directly affect user interests. #### Vaults V2 Governance [#vaults-v2-governance] Vaults V2 introduces a refined role system with clear separation of concerns: * The **Owner** appoints key roles (Curator and Sentinels) but does not inherit their powers. A compromised Owner can replace the Curator, who controls the vault's strategy. * The **Curator** configures adapters, caps, fees, allocators, and gates. Most Curator actions are subject to [timelocks](/curate/concepts/timelock/), giving depositors time to react before changes take effect. * The **Allocators** execute asset allocation between enabled adapters within Curator-set limits. They cannot introduce new risks but can influence yield and liquidity by moving assets between approved yield sources. * The **Sentinels** provide emergency de-risking capabilities. They can revoke pending timelocked actions, decrease caps, and deallocate assets without being allocators. **Key governance mechanisms in V2:** * **Timelocks**: Critical configuration changes require a submission followed by a waiting period before execution. This ensures depositors can always withdraw their assets before changes take effect. * **Abdication**: The Curator can permanently disable specific timelocked functions via [abdication](/curate/tutorials-v2/abdicate-gates/), irreversibly locking configurations. While this provides strong guarantees against future changes, it also removes flexibility. * **Gates**: [Gate contracts](/curate/concepts/gates/) can restrict share and asset transfers. A misconfigured gate could lock users out of deposits or withdrawals. * **Adapter Registry**: Vaults can be restricted to adapters from a specific [registry](/curate/concepts/adapter-registry/). This limits what yield sources the Curator can enable but relies on the registry's governance. * **Fee Caps**: Performance fees are capped at 50% of interest, and management fees at 5% annually, limiting maximum fee exposure for depositors. When depositing into a Morpho Vault, it is important to conduct thorough due diligence on the vault's settings and its allocation strategy, as well as to stay up to date with its changes. For legacy Vault V1 governance details, use the archived curator documentation in [Curate](/curate/tutorials-v1/vault-creation/). --- # Get book Source: https://docs.morpho.org/api/midnight/books/get-book {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get quote Source: https://docs.morpho.org/api/midnight/books/get-quote {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List book price levels Source: https://docs.morpho.org/api/midnight/books/list-book-price-levels {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List book takeable offers Source: https://docs.morpho.org/api/midnight/books/list-book-takeable-offers {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List books Source: https://docs.morpho.org/api/midnight/books/list-books {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List maker takeable offers Source: https://docs.morpho.org/api/midnight/books/list-maker-takeable-offers {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market state Source: https://docs.morpho.org/api/midnight/markets/get-market-state {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get market Source: https://docs.morpho.org/api/midnight/markets/get-market {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List markets Source: https://docs.morpho.org/api/midnight/markets/list-markets {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Validate a mempool payload Source: https://docs.morpho.org/api/midnight/mempool/validate-a-mempool-payload {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List Midnight offer groups for a user Source: https://docs.morpho.org/api/midnight/offer-groups/list-midnight-offer-groups-for-a-user {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get Midnight position performance Source: https://docs.morpho.org/api/midnight/positions/get-midnight-position-performance {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Get position Source: https://docs.morpho.org/api/midnight/positions/get-position {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List market positions Source: https://docs.morpho.org/api/midnight/positions/list-market-positions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List positions Source: https://docs.morpho.org/api/midnight/positions/list-positions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List market transactions Source: https://docs.morpho.org/api/midnight/transactions/list-market-transactions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # List user transactions Source: https://docs.morpho.org/api/midnight/transactions/list-user-transactions {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} --- # Interest Rates Source: https://docs.morpho.org/developers/borrow/concepts/interest-rates Understanding how interest rates are determined and how they affect a borrower's position is fundamental to building a safe and transparent borrow integration. In Morpho, the interest a borrower pays is dictated by the market's **Interest Rate Model (IRM)**. ## The Role of the Interest Rate Model (IRM) [#the-role-of-the-interest-rate-model-irm] Each Morpho Market is created with a specific, immutable IRM. This smart contract contains the logic that dynamically calculates the borrow interest rate based on market conditions, primarily the **utilization rate**. * **Utilization Rate:** The ratio of total borrowed assets to total supplied assets in a market. $$ \text{Utilization} = \frac{\text{Total Borrows}}{\text{Total Supply}} $$ * **Governance-Approved IRMs:** Only IRMs that have been approved by Morpho Governance can be used to create new markets. Currently, the primary model is the `AdaptiveCurveIRM`. ### The AdaptiveCurveIRM [#the-adaptivecurveirm] The `AdaptiveCurveIRM` is designed to maintain market utilization around a target of **90%**. * **When utilization \< 90%:** The borrow rate gradually decreases to incentivize more borrowing. * **When utilization > 90%:** The borrow rate rapidly increases to encourage repayments and attract more supply. This mechanism ensures that markets remain capital-efficient while having enough liquidity for withdrawals. For a deeper dive into the mathematical formulas and adaptive mechanics, see the **[IRM Concept Page](/learn/concepts/irm/)**. ## How Interest Accrues on Debt [#how-interest-accrues-on-debt] For a borrower, the most important takeaway is that **interest is constantly accruing**, increasing their total debt over time. This directly impacts their position's health. The process is as follows: ### 1. Rate Calculation [#1-rate-calculation] The IRM calculates the instantaneous `borrowRate` based on the market's current utilization. ### 2. Interest Accrual [#2-interest-accrual] This rate is applied to the borrower's debt continuously. The amount of interest accrued increases the `totalBorrowAssets` in the market and, proportionally, the asset value of each borrower's `borrowShares`. ### 3. Impact on Health Factor [#3-impact-on-health-factor] As the debt value increases due to accrued interest, the user's **LTV rises** and their **Health Factor falls**, even if collateral and asset prices remain stable. $$ \text{Health Factor} = \frac{\text{Collateral Value} \times \text{LLTV}}{\text{Initial Debt} + \text{Accrued Interest}} $$ This is a critical concept to communicate to users: their position can become riskier over time simply from interest accrual. ## Onchain State and `accrueInterest` [#onchain-state-and-accrueinterest] The Morpho contract does not update interest for every block to save gas. Instead, interest is calculated and applied only when a market interaction occurs via the `_accrueInterest` internal function. This function is triggered by actions like `borrow`, `repay`, `supply`, and `withdraw`. **What this means for your integration:** When you fetch a user's position from the contract, the `totalBorrowAssets` value reflects the state at the *last interaction*. To get the up-to-the-second debt value, you must account for the interest accrued since the `lastUpdate` timestamp. Jump on the tutorials to learn how to accrue the interests up to the last block. See the [Get Data Tutorial](/developers/borrow/tutorials/get-data#onchain--sdk-required-for-real-time-accuracy) for a practical example. ## Integration Best Practices [#integration-best-practices] * **Display Real-Time APY:** Don't just show the instantaneous borrow APY, which can be volatile. Provide users with a time-averaged APY (e.g., 6-hour average) to give a more realistic view of their borrowing costs. * **Educate Users on Accruing Debt:** Your UI should make it clear that the user's debt amount is continuously increasing and that this affects their Health Factor. * **Simulate Interest Impact:** When users are opening a position, provide them with projections of how their Health Factor might change over time due to interest accrual, especially in volatile rate environments. By correctly implementing and displaying interest rate mechanics, you empower users to manage their borrow positions effectively and safely. --- # Liquidation Source: https://docs.morpho.org/developers/borrow/concepts/liquidation Liquidation is one of the core mechanism in Morpho. It protects lenders' capital by ensuring that undercollateralized loans are repaid, thereby maintaining the solvency of each market. For any developer integrating a borrowing feature, understanding and clearly communicating how liquidation works is paramount. When a borrower's position becomes too risky, the protocol allows a third party-a **liquidator**-to step in, repay the debt, and seize the borrower's collateral at a discount. ## When Does Liquidation Occur? [#when-does-liquidation-occur] A position becomes eligible for liquidation the moment its **Health Factor drops to 1 or below**. As a reminder, this happens when the Loan-to-Value (LTV) of a position meets or exceeds the market's immutable Liquidation Loan-to-Value (LLTV) threshold. $$ \text{If} \quad \frac{\text{Debt Value}}{\text{Collateral Value}} \ge \text{LLTV} \quad \implies \quad \text{Position is Liquidatable} $$ This can be caused by: * A decrease in the price of the collateral asset. * An increase in the value of the debt due to accrued interest. ## The Liquidation Process [#the-liquidation-process] Liquidation on Morpho is a straightforward, economically-driven process. It is not an auction; it's a direct transaction executed by the first liquidator to act. ### 1. An Unhealthy Position is Identified [#1-an-unhealthy-position-is-identified] A liquidator (typically an automated bot) detects a position where the Health Factor is ≤ 1. ### 2. Liquidator Repays the Debt [#2-liquidator-repays-the-debt] The liquidator calls the `liquidate` function on the Morpho contract, repaying a portion or all of the borrower's debt using the loan asset. ### 3. Liquidator Seizes Collateral at a Discount [#3-liquidator-seizes-collateral-at-a-discount] In return for repaying the debt, the liquidator is allowed to seize an equivalent value of the borrower's collateral, plus a bonus. This bonus is the liquidator's incentive and profit. ### 4. The Borrower's Position is Updated [#4-the-borrowers-position-is-updated] The borrower's debt is reduced or eliminated, and their collateral is reduced by the amount seized. ### The Liquidation Incentive Factor (LIF) [#the-liquidation-incentive-factor-lif] The "discount" or "bonus" a liquidator receives is determined by the **Liquidation Incentive Factor (LIF)**. This factor is calculated based on the market's LLTV, ensuring that riskier markets (with higher LLTVs) offer a smaller bonus to prevent cascading liquidations.
LIF vs LLTV graph
For a market with an **LLTV of 86%**, the **LIF is approximately 1.05**, meaning the liquidator receives a **5% bonus** on the collateral they seize. This entire incentive goes to the liquidator; the Morpho protocol takes no fee. ### Example: A Liquidation Scenario [#example-a-liquidation-scenario] * **Initial State**: A user has a position in a market with an LLTV of 86%. Their debt has grown to **$87,000**, while their collateral value has dropped to **$100,000**. * **Health Check**: The LTV is 87% (`87k / 100k`), which is greater than the 86% LLTV. The position is liquidatable. * **Liquidation**: A liquidator repays the full **$87,000** debt. * **Collateral Seized**: The liquidator seizes `$87,000 * 1.05` (LIF) = **$91,350** worth of the borrower's collateral. * **Outcome**: * **Borrower**: Their debt is cleared, but they lose $91,350 of their $100,000 collateral, incurring a loss of **$4,350**. * **Liquidator**: Profits by **$4,350** (minus gas and transaction costs). ### Bad Debt [#bad-debt] In extreme cases where the collateral's value drops so fast that it becomes less than the debt (`LTV > 100%`), a liquidation might not cover the full loan. The remaining unpaid debt is known as **bad debt**. This represents a loss for lenders in that market. Morpho's design, including its risk-isolated markets and conservative LLTVs, aims to make this a rare event. ## Integration Best Practices for Developers [#integration-best-practices-for-developers] Your primary goal as an integrator is to help your users avoid liquidation. 1. **Prioritize Health Factor Display:** The Health Factor should be the most prominent metric for any user with an open borrow position. Use clear visual cues (colors, gauges) to communicate risk. 2. **Educate About the "Point of No Return":** Clearly display the market's LLTV and the user's current LTV. The user must understand that crossing the LLTV threshold is the trigger for liquidation. 3. **Implement Proactive Alerts:** Build notifications within your application to warn users when their Health Factor drops to a cautionary level (e.g., below 1.1). 4. **Simulate Transactions:** Before a user confirms a `borrow` or `withdrawCollateral` transaction, show them the resulting Health Factor. This prevents them from unknowingly putting their position at risk. By treating liquidation as a core part of the user experience, you can build a safer and more trustworthy borrowing platform. --- # Collateral, LTV & Health Source: https://docs.morpho.org/developers/borrow/concepts/ltv When a user borrows from a Morpho Market, their position's safety is determined by the relationship between their collateral, their debt, and the market's risk parameters. Understanding and clearly displaying these metrics is one of the most critical responsibilities when building a borrow integration. This page explains the core concepts of Collateral, Loan-to-Value (LTV), and Health Factor. ## Collateral [#collateral] In Morpho, **collateral** is the asset a user supplies to a market to secure their loan. For example, in a `wstETH/WETH` market, `wstETH` is the collateral. This collateral can protect lenders by enabling the protocol to recover assets if the borrower defaults. A user's collateral is specific to each market; it is not cross-margined at the market level. Also, the action to supply collateral does not generate yield. ## Understanding Loan-To-Value (LTV) [#understanding-loan-to-value-ltv] It's essential to understand how LTV is calculated and what it represents. ### How to Calculate LTV [#how-to-calculate-ltv] The Loan-To-Value (LTV) ratio is a key risk metric that measures the proportion of debt relative to collateral value. To calculate the LTV of a position on Morpho, use the following formula: $$ \text{LTV} = \frac{\text{BORROWED\_AMOUNT}}{\text{COLLATERAL\_VALUE\_IN\_LOAN\_TOKEN}} \times 100\% $$ * **`BORROWED_AMOUNT`** The amount of borrowed assets of the user (in token base units) * **`COLLATERAL_VALUE_IN_LOAN_TOKEN`** The value of the collateral in terms of the loan token The collateral value in loan token units is calculated as: $$ \text{COLLATERAL\_VALUE\_IN\_LOAN\_TOKEN} = \frac{\text{COLLATERAL\_AMOUNT } \times \text{ ORACLE\_PRICE}}{\text{ORACLE\_PRICE\_SCALE}} $$ * **`COLLATERAL_AMOUNT`** The amount of collateral assets provided by the user (in token base units) * **`ORACLE_PRICE`** The oracle price returned by the oracle of the market (scaled by ORACLE\_PRICE\_SCALE) * **`ORACLE_PRICE_SCALE`** A scaling factor of 10^36 used by the protocol for price normalization ### Liquidation Loan-to-Value (LLTV) [#liquidation-loan-to-value-lltv] The **Liquidation Loan-to-Value (LLTV)** is the maximum LTV a position can reach before it becomes eligible for liquidation. It is a fixed, immutable parameter for each market, chosen from a governance-approved list at the time of creation. The rule is simple and absolute: **If `LTV` ≥ `LLTV`, the position can be liquidated.** For example, if a market's LLTV is 86%, a user's position is at risk of liquidation as soon as their LTV reaches or exceeds 86%. ### Health Factor [#health-factor] The Health Factor is another crucial metric that indicates how close a position is to liquidation: $$ \text{HEALTH\_FACTOR} = \frac{\text{COLLATERAL\_VALUE\_IN\_LOAN\_TOKEN} \times \text{LLTV}}{\text{BORROWED\_AMOUNT}} $$ * **`LLTV`** The Liquidation Loan-To-Value threshold set for the market (e.g., 0.86 or 86%), expressed as a WAD (10^18 scaled value, like 860000000000000000) A position is healthy when the Health Factor is greater than 1.0. When it falls below 1.0, the position becomes eligible for liquidation. * For oracle implementation details, see the [dedicated oracle section](/curate/tutorials-market-v1/deploying-oracle/) * WAD represents a common scaling factor in DeFi of 10^18 used for representing decimal numbers in integer arithmetic Let's walk through a concrete example of calculating LTV and Health Factor for a position on Morpho: **Given values (with assumptions):** * Borrowed amount: 150,000 USDC (150,000,000,000 base units with 6 decimals) * Collateral amount: 2 cbBTC (200,000,000 base units with 8 decimals) * Oracle price: 1 × 10^39 (means 1 cbBTC = 100000 USDC). This is supposedly the value returned by the price function in the oracle used in the related market * Oracle price scale: 10^36 * LLTV: 86% (expressed as 0.86 × 10^18 or 860,000,000,000,000,000 in WAD units) ### Calculate the collateral value in loan token units [#calculate-the-collateral-value-in-loan-token-units] ```typescript // All calculations use BigInt (suffixed with 'n') to handle large numbers precisely const collateralValueInLoanToken = (collateralAmount * oraclePrice) / ORACLE_PRICE_SCALE; // = (200,000,000n * 1,000,000,000,000,000,000,000,000,000,000,000,000,000n) / 10n**36n // = 200,000,000,000 base units of loan token (USDC) ``` ### Calculate the current LTV [#calculate-the-current-ltv] ```typescript // Constants const WAD = 10n ** 18n; // Standard scaling factor (10^18) // Example values const borrowedAmount = 150_000_000_000n; // 150 billion units (e.g., USDC with 6 decimals) const collateralValueInLoanToken = 200_000_000_000n; // 200 billion units /** * Current LTV Calculation * ---------------------- * * Step 1: Calculate the raw LTV ratio, preserving precision with WAD */ const currentLTV = (borrowedAmount * WAD) / collateralValueInLoanToken; // = (150,000,000,000n * 10n**18n) / 200,000,000,000n // = 750,000,000,000,000,000n (scaled by WAD, representing 0.75 or 75%) /** * Option 1: Theoretical Formula * ---------------------------- * In theory, the conversion to percentage is simply: */ // Theoretical formula (doesn't work directly with BigInt) // currentLTVPercentage = (currentLTV / WAD) * 100 // Implementation of theoretical formula (using Number conversion) const theoreticalLTVPercentage = (Number(currentLTV) / Number(WAD)) * 100; // = 75.0000% /** * Option 2: Practical Display Implementation * ---------------------------------------- * For precise display with 4 decimal places, we: * 1. Scale up by the display factor (percentage * decimal precision) * 2. Divide by WAD to normalize * 3. Convert to Number and adjust for decimal places */ // Scale factor = 100 (for percentage) * 10000 (for 4 decimal places) = 1,000,000 const displayScaleFactor = 1_000_000n; const currentLTVPercentageScaled = (currentLTV * displayScaleFactor) / WAD; // = 750_000n (represents 75.0000%) // Convert the scaled BigInt result to a human-readable number const displayLTVPercentage = Number(currentLTVPercentageScaled) / 10000; // = 75.0000% (when displayed with 4 decimal places) ``` ### Calculate the health factor [#calculate-the-health-factor] ```typescript // Since LLTV is stored as a WAD, we need to account for scaling const healthFactor = (collateralValueInLoanToken * lltv) / borrowedAmount; // = (200,000,000,000,000n * 860,000,000,000,000,000n) / 150,000,000,000n // = 1,146,666,666,666,666,666 (scaled by WAD) // Convert to decimal const healthFactorDisplay = Number(healthFactor) / Number(WAD); // = 1.1467 (when converted to a human-readable decimal) ``` Since the Health Factor is greater than 1.0 (1.1467), this position is healthy and has a safety margin before liquidation could occur. #### Summary of Position: [#summary-of-position] * Current LTV: 75.00% * Max LTV (LLTV): 86.00% * Health Factor: 1.1467 * Status: Healthy * Liquidation Buffer: 11.00% (difference between current LTV and max LTV) This shows a position with an LTV of 75%, which is below the LLTV threshold of 86%. The Health Factor of 1.1467 confirms that the position is healthy with a 11% safety margin before liquidation. * For oracle implementation details, see the [dedicated oracle section](/curate/tutorials-market-v1/deploying-oracle/) * For examples of LTV calculations in Solidity, refer to [this code](https://github.com/morpho-org/morpho-blue/blob/12b8a453643d5ef9d55abd88b9f8cfa866882aa5/src/Morpho.sol#L532-L536) ## The Role of Oracles [#the-role-of-oracles] The accuracy of LTV and Health Factor calculations depends on the **Oracle Price**. * **Dynamic Pricing:** The oracle provides the real-time exchange rate between the collateral and loan assets. This price is the most dynamic variable in the health calculation. * **Oracle Complexity:** The oracle for a market might not be a single price feed. It could be a combination of feeds (e.g., `wstETH -> stETH` and `stETH -> ETH`) or rely on other onchain data. * **Risk Exposure:** The reliability of your LTV and Health Factor display is a direct reflection of the oracle's reliability. Any latency, inaccuracy, or manipulation of the oracle's price can directly impact user positions. When displaying market information, it is crucial to also provide transparency about the oracle being used. ## Integration Best Practices [#integration-best-practices] For any application with a borrow interface, a primary goal should be to help users avoid liquidation. 1. **Display Health Factor Prominently:** This should be the most visible metric on any position management dashboard. Use visual aids like colors (green, yellow, red) or progress bars to indicate safety levels. 2. **Clearly State the LLTV:** Users must know the exact "point of no return" for their position. Display the market's `LLTV` alongside the user's current `LTV`. 3. **Implement Proactive Alerts:** When a user's Health Factor drops below a certain safe threshold (e.g., `1.1`), trigger notifications in your UI or via other channels to prompt them to add more collateral or repay part of their loan. 4. **Incorporate Safety Buffers:** Do not allow users to borrow the maximum amount that would place their Health Factor exactly at `1.0`. Your interface should enforce a safety margin, for example, by limiting borrows to a Health Factor of `1.05` or higher. 5. **Simulate Transactions:** Before submitting a `borrow` or `withdrawCollateral` transaction, simulate its effect on the user's Health Factor. Show the user what their new Health Factor will be *before* they sign the transaction. By following these best practices, you can build a borrow experience that is not only functional but also safe and transparent for your users. **Next Up:** Learn what happens when the Health Factor drops below 1.0 in the **[Liquidation concept page](/learn/concepts/liquidation/)**. --- # Market Mechanics Source: https://docs.morpho.org/developers/borrow/concepts/market-mechanics Interacting with Morpho Markets involves a set of core functions and concepts that govern how assets are supplied, borrowed, and tracked. Unlike ERC4626 vaults, which have a standardized interface, Morpho's market interactions are defined by its unique internal accounting system. This page explains the fundamental mechanics you need to understand to build a robust borrowing integration. ## Core Data Structures [#core-data-structures] At the heart of Morpho are two key structs that define the state of every market and every user's position within it. ### `Market` Struct [#market-struct] This struct tracks the overall state of a single, isolated market. ```solidity struct Market { uint128 totalSupplyAssets; uint128 totalSupplyShares; uint128 totalBorrowAssets; uint128 totalBorrowShares; uint128 lastUpdate; uint128 fee; } ``` * `totalSupply/BorrowAssets`: The total amount of the underlying token supplied or borrowed. This value changes as interest accrues. * `totalSupply/BorrowShares`: The total number of internal accounting units (shares) held by suppliers or borrowers. This value only changes with user interactions. ### `Position` Struct [#position-struct] This struct tracks an individual user's position within a specific market. ```solidity struct Position { uint256 supplyShares; uint128 borrowShares; uint128 collateral; } ``` * `collateral`: The amount of collateral the user has supplied *to this specific market*. ## Assets vs. Shares: The Core Mechanic [#assets-vs-shares-the-core-mechanic] The most critical concept to grasp is the relationship between **Assets** and **Shares**. This dual-accounting system is how Morpho manages positions and accrues interest. ### Assets [#assets] * **Definition**: The actual, underlying tokens (e.g., USDC, WETH) that users interact with. * **Use Case**: Ideal for user-facing interactions where a specific token amount is desired (e.g., "I want to borrow 1,000 USDC"). ### Shares [#shares] * **Definition**: Internal, non-transferable accounting units that represent a user's proportional stake in the market's total supply or debt. * **How it works**: When you supply assets, you "buy" shares at the current exchange rate. As interest accrues in the market, the value of each share (its exchange rate back to assets) increases. * **Use Case**: Ideal for protocol-level interactions and for ensuring full repayment or withdrawal, as it avoids rounding errors ("dust"). ### The Relationship: Share Price [#the-relationship-share-price] The "price" of a share is its exchange rate to the underlying asset. This price is dynamic and increases as interest accrues. ```solidity // This is a conceptual calculation; the contract uses SharesMathLib for precision. sharePrice = totalAssets / totalShares; ``` * For **suppliers**, the value of their shares grows over time, representing their earned yield. * For **borrowers**, the asset value of their debt grows over time, representing the interest they owe. ## Core Functions & Their Dual Nature [#core-functions--their-dual-nature] Most core functions in Morpho allow you to specify an amount in either `assets` or `shares`. You must provide a value for one and zero for the other. ### `supply` and `borrow` [#supply-and-borrow] ```solidity function supply(uint256 assets, uint256 shares, ...); function borrow(uint256 assets, uint256 shares, ...); ``` * **Use `assets` > 0, `shares` = 0:** When a user wants to supply or borrow a precise amount of tokens. This is the most common use case for dApps. * **Use `assets` = 0, `shares` > 0:** For advanced use cases where you need to mint a precise number of shares. ### `repay` and `withdraw` [#repay-and-withdraw] ```solidity function repay(uint256 assets, uint256 shares, ...); function withdraw(uint256 assets, uint256 shares, ...); ``` * **Use `assets` > 0, `shares` = 0:** For partial repayments or withdrawals where a specific token amount is needed. * **Use `assets` = 0, `shares` > 0:** **Recommended for full repayments/withdrawals.** By specifying the user's exact share balance, you ensure their position is completely closed without leaving dust. ## Collateral and Health [#collateral-and-health] * **`supplyCollateral` & `withdrawCollateral`**: These functions are straightforward and only operate on `assets`. Collateral in Morpho does not earn yield and thus does not use a share-based system. * **Position Health**: A user's ability to borrow or withdraw collateral is determined by their Health Factor, which is a function of their collateral value, debt value, and the market's `lltv`. To learn more about how position health is calculated and managed, see the **[Collateral, LTV & Health concept page](/developers/borrow/concepts/ltv/)**. ## Practical Implementation Snippets [#practical-implementation-snippets] ### Supplying Collateral & Borrowing (Asset-First) [#supplying-collateral--borrowing-asset-first] ```typescript // Conceptual TypeScript using a web3 library like Viem // 1. Approve collateral tokens await client.writeContract({ functionName: 'approve', args: [morphoAddress, collateralAmount] }); // 2. Supply collateral await client.writeContract({ functionName: 'supplyCollateral', args: [marketParams, collateralAmount, userAddress] }); // 3. Borrow assets const { result } = await client.simulateContract({ functionName: 'borrow', args: [marketParams, borrowAmount, 0, userAddress, userAddress] // assets > 0, shares = 0 }); await client.writeContract(result.request); ``` ### Full Repayment (Shares-First) [#full-repayment-shares-first] ```typescript // 1. Fetch the user's current borrow shares const { borrowShares } = await client.readContract({ functionName: 'position', args: [marketId, userAddress] }); // 2. Approve the loan token for repayment // The amount to approve should be slightly more than the expected asset value of the shares // to account for interest accrued since the last block. const repayAmountAssets = await client.readContract({ functionName: 'toAssetsUp', // using a helper or SDK function args: [borrowShares, market.totalBorrowAssets, market.totalBorrowShares] }); await client.writeContract({ functionName: 'approve', args: [morphoAddress, repayAmountAssets] }); // 3. Repay the full debt by specifying the exact shares await client.writeContract({ functionName: 'repay', args: [marketParams, 0, borrowShares, userAddress] // assets = 0, shares > 0 }); ``` ## Best Practices [#best-practices] * **For User-Facing Actions**: Use the `assets` parameter for `supply`, `borrow`, and partial `repay`/`withdraw` as it's more intuitive for users. * **For Closing Positions**: Use the `shares` parameter for full `repay` and `withdraw` operations to ensure the position is fully closed. * **Slippage**: When converting between assets and shares, be aware of potential slippage due to interest accrual between transaction simulation and execution. * **Interest Accrual**: Remember that `totalBorrowAssets` and `totalSupplyAssets` are only updated when an interaction triggers `_accrueInterest`. For the most up-to-date values, use a library like `MorphoBalancesLib` or the Morpho SDK. By understanding these core mechanics, you can build safe, efficient, and user-friendly applications on top of Morpho Markets. --- # Rewards for Borrowers Source: https://docs.morpho.org/developers/borrow/concepts/rewards Borrowers on Morpho Markets can earn rewards that offset borrowing costs or even generate net positive yields. These incentives are designed to bootstrap markets, attract liquidity, and encourage specific borrowing behaviors. ## Overview [#overview] As a borrower on Morpho, you can earn rewards for borrowing an asset from a market. **Net Cost Calculation:** ``` Net Borrow APY = Borrow APY - Borrow Rewards APR ``` It is also possible to incentivize a simple collateral deposit (without the borrow action required). In favorable conditions, rewards can exceed borrow costs, resulting in **negative net APY** (borrowers earn while borrowing). Rewards are claimable on the side via the Merkl distribution system and do not impact user LTV. Even though the Net Borrow APY can be negative, user loan will increase and so its LTV. **Borrow Rewards:** * Incentives for borrowing an asset * Accrue based on outstanding borrow balance * Offset borrowing costs directly ### Net Cost Display Priority [#net-cost-display-priority] For borrow products, users care most about **net cost after all rewards**: * Display net APY prominently * Highlight when borrowing becomes profitable (negative net APY) * Show breakdown: borrow cost - borrow rewards ## Distribution System [#distribution-system] All Morpho rewards are distributed through **Merkl**, which updates every 8 hours and handles all active programs. If you have unclaimed rewards from historical Morpho programs (pre-Merkl migration), claim them via [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). The claiming process is **identical** for all Morpho users, regardless of whether they're depositors, borrowers, or collateral suppliers. **For complete details on distribution systems, see:** * [Distribution System](/developers/rewards/concepts/distribution-system) * [Reward Campaigns](/developers/rewards/concepts/reward-campaigns) ## Learn More [#learn-more] For comprehensive information about integrating rewards: * **[Rewards Integration Guide](/developers/rewards/get-started)**: Complete overview and integration paths * **[Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data)**: Complete API queries and calculations * **[Claim Rewards](/developers/rewards/tutorials/claim-rewards)**: Claim implementation via Merkl * **[Integrate Display](/developers/rewards/tutorials/integrate-display)**: UI/UX best practices ## Implementation [#implementation] For practical implementation guidance specific to borrow products, see: * [Integrate Rewards for Borrow Products](/developers/borrow/tutorials/rewards) --- # Guide: Integrating Gasless Crypto-Backed Loans with Gelato Source: https://docs.morpho.org/developers/borrow/guides/gelato This tutorial provides a complete guide for developers to integrate noncustodial, crypto-backed loans into their applications using Morpho's lending protocol and Gelato's Smart Wallet SDK. This powerful combination allows you to offer your users a seamless, Web2-like experience with features like social logins and gasless transactions, all while leveraging Morpho's robust and efficient onchain infrastructure. We will walk through the entire process, from initial setup to implementing the core supply and borrow functionalities. **Final Product:** By the end of this guide, you'll be able to build a user flow similar to the official **[Morpho x Gelato Demo](https://morpho-aa.demo.gelato.cloud/)**.
## Architecture Overview [#architecture-overview] The integration relies on two key components working in concert: 1. **Morpho Protocol**: Provides the core, permissionless lending infrastructure. Morpho's smart contracts handle all the logic for supplying collateral, borrowing assets, managing interest rates, and ensuring protocol solvency. 2. **Gelato Smart Wallet SDK**: Acts as the abstraction layer between your application and the blockchain. It converts traditional wallets (EOAs) into smart accounts, enabling features like one-click social logins, gas fee sponsorship, and batching multiple transactions into a single, atomic operation. Here is a high-level view of the interaction flow: ``` ┌─────────────────┐ │ End User │ └─────────────────┘ │ │ Interacts with ▼ ┌─────────────────┐ │ Your Application│ └─────────────────┘ │ │ Uses ▼ ┌─────────────────┐ │ Gelato Smart │ │ Wallet SDK │ └─────────────────┘ │ │ 1. Creates Smart Account & Session │ 2. Bundles & Sponsors Transactions ▼ ┌─────────────────┐ │ Blockchain │ └─────────────────┘ │ │ Executes atomic calls ▼ ┌─────────────────┐ │ Morpho Protocol │ │ Contracts │ │ (supply, borrow)│ └─────────────────┘ ``` ### Part 1: Setup and Configuration [#part-1-setup-and-configuration] This guide assumes you are building a React application with TypeScript. A correct setup is critical for a stable and secure application. ### Step 1: Install Dependencies [#step-1-install-dependencies] First, install all necessary packages from Gelato, Dynamic, and other required libraries. ```bash npm install @gelatonetwork/smartwallet @dynamic-labs/sdk-react-core @dynamic-labs/wagmi-connector @tanstack/react-query viem wagmi ``` ### Step 2: Obtain API Keys [#step-2-obtain-api-keys] You will need two API keys: 1. **Gelato API Key**: This key is used to sponsor transactions (i.e., pay for your users' gas fees). * Go to the **[Gelato Relay App](https://relay.gelato.network/)**. * Create a new app, select the required networks, and copy your Sponsor API Key. 2. **Dynamic Environment ID**: This key connects your application to Dynamic's wallet-as-a-service infrastructure. * Sign up at the **[Dynamic Dashboard](https://www.dynamic.xyz/)**. * Create a new project and copy your Environment ID. Never hardcode API keys or sensitive IDs in your client-side code. Use environment variables with a framework-specific prefix (e.g., `NEXT_PUBLIC_` or `VITE_`). ### Step 3: Define Constants and ABIs [#step-3-define-constants-and-abis] For a clean implementation, define your contract addresses, ABIs, and market parameters in a separate constants file. ```typescript // constants.ts import { base, arbitrum, optimism, polygon, scroll } from "viem/chains"; // 1. Supported Networks export const SUPPORTED_NETWORKS = { [base.id]: base, [arbitrum.id]: arbitrum, [optimism.id]: optimism, [polygon.id]: polygon, [scroll.id]: scroll, }; // 2. Contract Addresses (Example for Base Mainnet) export const MORPHO_BLUE_ADDRESS = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; export const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // Note: Use the correct USDC address for your network export const CBBTC_ADDRESS = "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"; // Note: Use the correct cbBTC address for your network // 3. Morpho Market Parameters (Example for cbBTC/USDC market) // NOTE: For a production app, fetch these dynamically. See the "Get Data" tutorials. export const CBBTC_USDC_MARKET_PARAMS = { loanToken: USDC_ADDRESS, collateralToken: CBBTC_ADDRESS, oracle: "0x...", // The specific oracle for this market irm: "0x...", // The specific IRM for this market lltv: BigInt("860000000000000000"), // 86% }; // 4. Minimal ABI fragments export const ERC20_ABI = [ { "inputs": [{"name":"spender","type":"address"}, {"name":"amount","type":"uint256"}], "name": "approve", "outputs": [{"name":"","type":"bool"}], "stateMutability": "nonpayable", "type": "function", }, ]; export const MORPHO_BLUE_ABI = [ // supplyCollateral, borrow, repay etc. // NOTE: A more complete ABI is needed. See official Morpho resources. ]; ``` ### Step 4: Configure the Context Provider [#step-4-configure-the-context-provider] Wrap your application's root with the `GelatoSmartWalletContextProvider` and `QueryClientProvider`. This makes the smart wallet client and user session available throughout your component tree. ```tsx // In your main App.tsx or a layout component import { GelatoSmartWalletContextProvider } from "@gelatonetwork/smartwallet"; import { dynamic } from "@gelatonetwork/smartwallet/adapters"; import { wagmi } from "@gelatonetwork/smartwallet/networks"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { http } from "viem"; import { SUPPORTED_NETWORKS } from "./constants"; const queryClient = new QueryClient(); const chains = Object.values(SUPPORTED_NETWORKS); const transports = chains.reduce((acc, chain) => { acc[chain.id] = http(); return acc; }, {} as Record>); const App = ({ children }: { children: React.ReactNode }) => { return ( {children} ); }; ``` *** ### Part 2: Building a Complete Lending Component [#part-2-building-a-complete-lending-component] Now, let's build a complete React component that handles user authentication and the core borrow functionality. ```tsx // components/MorphoLendingComponent.tsx import React, { useState } from "react"; import { useGelatoSmartWalletProviderContext } from "@gelatonetwork/smartwallet"; import { encodeFunctionData, parseUnits } from "viem"; import { sponsored } from "@gelatonetwork/smartwallet"; // Import constants and ABIs from your constants file import { CBBTC_ADDRESS, USDC_ADDRESS, MORPHO_BLUE_ADDRESS, CBBTC_USDC_MARKET_PARAMS, ERC20_ABI, MORPHO_BLUE_ABI, } from "../constants"; const GELATO_API_KEY = process.env.NEXT_PUBLIC_GELATO_API_KEY as string; export const MorphoLendingComponent = () => { const [borrowAmount, setBorrowAmount] = useState(""); const [collateralAmount, setCollateralAmount] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [txHash, setTxHash] = useState(null); const { client: smartWalletClient, smartWallet, connect, disconnect, isConnected, } = useGelatoSmartWalletProviderContext(); const handleBorrow = async () => { if (!smartWalletClient || !smartWallet || !borrowAmount || !collateralAmount) return; setLoading(true); setError(null); setTxHash(null); try { const parsedCollateral = parseUnits(collateralAmount, 8); // cbBTC has 8 decimals const parsedBorrow = parseUnits(borrowAmount, 6); // USDC has 6 decimals const calls = [ // 1. Approve Morpho to spend the collateral { to: CBBTC_ADDRESS as `0x${string}`, data: encodeFunctionData({ abi: ERC20_ABI, functionName: "approve", args: [MORPHO_BLUE_ADDRESS, parsedCollateral], }), }, // 2. Supply collateral to the market { to: MORPHO_BLUE_ADDRESS as `0x${string}`, data: encodeFunctionData({ abi: MORPHO_BLUE_ABI, functionName: "supplyCollateral", args: [CBBTC_USDC_MARKET_PARAMS, parsedCollateral, smartWallet.address, "0x"], }), }, // 3. Borrow the loan asset { to: MORPHO_BLUE_ADDRESS as `0x${string}`, data: encodeFunctionData({ abi: MORPHO_BLUE_ABI, functionName: "borrow", args: [ CBBTC_USDC_MARKET_PARAMS, parsedBorrow, BigInt(0), // shares smartWallet.address, // receiver smartWallet.address, // onBehalf ], }), }, ]; // Execute the bundled, sponsored transaction const response = await smartWalletClient.execute({ payment: sponsored(GELATO_API_KEY), calls, }); const receipt = await response.wait(); setTxHash(receipt.id); } catch (e: any) { console.error("Borrow failed:", e); setError(e.message || "An unknown error occurred."); } finally { setLoading(false); } }; // UI Rendering if (!isConnected) { return ; } return (

Borrow USDC with cbBTC Collateral

setCollateralAmount(e.target.value)} disabled={loading} /> setBorrowAmount(e.target.value)} disabled={loading} /> {txHash &&

Success! Transaction: {txHash}

} {error &&

Error: {error}

}
); }; ``` *** ### Part 3: Production Considerations [#part-3-production-considerations] #### Security Best Practices [#security-best-practices] * **Key Management**: Never expose private keys or API keys in client-side code. Use secure environment variables. * **Input Validation**: Always validate and sanitize user inputs before constructing transactions to prevent injection attacks. * **Slippage Protection**: For production applications, especially those involving swaps, it is crucial to implement slippage protection. The Gelato SDK can be combined with DEX aggregators that handle this. * **Dependency Management**: Keep all SDKs and libraries updated to their latest stable versions to benefit from security patches. #### Common Issues & Troubleshooting [#common-issues--troubleshooting] | Issue | Solution | | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | **Transaction fails silently** | Check the browser console for detailed error messages. Common causes include incorrect contract addresses or ABI definitions. | | **"Insufficient Allowance"** | Ensure the `approve` call is included in your bundle and the amount is sufficient. | | **"Provider Not Found"** | Verify that `GelatoSmartWalletContextProvider` correctly wraps your entire application or component tree. | | **Gas Estimation Fails** | This often points to an onchain error. Ensure the user's smart wallet is funded (if not sponsored) or that the contract logic is sound. | ## Key Resources & Next Steps [#key-resources--next-steps] You now have the building blocks to integrate a powerful, abstracted lending experience. To learn more, explore these resources: --- # Guide: How to Monetize Your Borrow Product Source: https://docs.morpho.org/developers/borrow/guides/monetize-borrow-product Borrow products can monetize the integration layer around Morpho Markets while keeping the underlying protocol permissionless. The first pattern to consider is a **borrow origination fee**: a one-time fee paid in the loan token when a borrower opens or increases a borrow position. This guide explains how the pattern works, where it can be implemented, and what builders must disclose, simulate, and secure before using it in production. The referenced contracts in [`morpho-org/morpho-snippets`](https://github.com/morpho-org/morpho-snippets/tree/main/src/morpho-blue) are educational examples and have not been audited. Treat them as implementation references, not production-ready contracts. ## What is a borrow origination fee? [#what-is-a-borrow-origination-fee] A borrow origination fee is charged once, at borrow time. The borrower receives the requested net amount, while the fee recipient receives an additional amount of the same loan token. For example, if a user wants to receive `1,000 USDC` and the origination fee is `200 bps`: * `feeAmount = 1,000 USDC * 200 / 10,000 = 20 USDC` * `totalBorrowed = 1,000 USDC + 20 USDC = 1,020 USDC` * the borrower receives `1,000 USDC` * the fee recipient receives `20 USDC` * the borrower's Morpho debt is `1,020 USDC`, and interest accrues on `1,020 USDC` This distinction is critical for UX: the fee is not separate from the debt. It consumes borrow capacity, changes the resulting LTV and health factor, and increases the amount that must later be repaid. ## Where the fee lives [#where-the-fee-lives] Morpho Blue does not enforce an application-level origination fee. The fee is implemented by the builder's transaction path around `Morpho.borrow`. The core mechanics are: ```solidity uint256 feeAmount = userAssets * feeBps / 10_000; uint256 totalBorrowed = userAssets + feeAmount; morpho.borrow(marketParams, totalBorrowed, 0, onBehalf, receiver); ``` The two important `borrow` parameters are: * `onBehalf`: the address whose borrow position increases. * `receiver`: the address that receives the borrowed loan tokens. A fee-charging integration borrows the gross amount, sends the net amount to the borrower, and routes the fee amount to the fee recipient. ## Implementation patterns [#implementation-patterns] There are two common ways to implement the pattern. | Pattern | Use when | How it works | Main risk to manage | | ------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Account-level execution, such as a smart wallet or EIP-7702 delegated EOA | Your product controls the user's account execution flow | The user account executes an atomic call that borrows the gross amount, keeps the net amount, and transfers the fee | The account policy or delegation must tightly restrict `feeBps`, `feeRecipient`, market, amount, and calldata | | EOA plus authorized router | The user uses a regular EOA and cannot execute custom account logic | The user authorizes a router with `morpho.setAuthorization(router, true)`. The router borrows on behalf of the user, receives the loan token, then transfers net amount and fee | The authorized router can increase the user's borrow position while authorization remains active, so authorization should be as short-lived as possible and the router must be minimal, audited, and easy to revoke | The educational snippets illustrate both approaches: * [`OriginationFeeExecutor.sol`](https://github.com/morpho-org/morpho-snippets/blob/main/src/morpho-blue/OriginationFeeExecutor.sol) shows an account-level EIP-7702 delegation target. The delegated EOA is the borrower and the receiver of the net borrowed assets. * [`OriginationFeeSnippets.sol`](https://github.com/morpho-org/morpho-snippets/blob/main/src/morpho-blue/OriginationFeeSnippets.sol) shows an owner-managed router. The EOA must authorize the router on Morpho before borrowing through it. ## Production checklist [#production-checklist] Before shipping a borrow origination fee, make the fee explicit and verifiable. ### User disclosure [#user-disclosure] Show the user, before signature: * net amount received by the borrower; * origination fee amount and recipient; * gross debt opened on Morpho; * resulting LTV or health factor after the fee; * estimated liquidation price or liquidation risk, if your product shows it; * repayment implication: interest accrues on the gross debt, not only the net amount received. Do not present the requested amount as the full debt if the transaction borrows `requested amount + fee`. ### Simulation and risk checks [#simulation-and-risk-checks] The transaction should be simulated with the gross borrow amount. Check that: * the market has enough liquidity for `totalBorrowed`; * the borrower has enough collateral for `totalBorrowed`, not only `userAssets`; * the position remains healthy after the fee; * the fee calculation handles token decimals and rounding consistently; * the fee recipient can receive and handle the loan token, including any follow-up transfer or recovery flow your product requires; * repayment and position displays use the actual borrow shares and accrued debt. ### Contract controls [#contract-controls] If fees are enforced by a contract, keep the trust surface small: * cap `feeBps` in the contract; * reject zero fee recipients and invalid fee values; * emit an event with borrower, market id, net amount, and fee amount; * use `SafeERC20` or equivalent checked wrappers for ERC-20 transfers; * avoid unnecessary upgradeability and mutable configuration; * protect fee configuration with a multisig, timelock, or immutable deployment, depending on your trust model; * test interest accrual on the gross debt, insufficient-collateral reverts, zero-amount behavior, and authorization revocation. ### Authorization safety [#authorization-safety] For the router pattern, `morpho.setAuthorization(router, true)` is powerful. The router can act on behalf of the user for Morpho actions while it remains authorized. A production router should therefore: * expose only the methods needed by your product; * keep fee parameters bounded and transparent; * never borrow to an arbitrary receiver controlled by an attacker; * for `setAuthorizationWithSig` or any other signature-based permission, bind the signature or policy to the intended chain ID and use the shortest practical deadline; * keep router authorization active only for as long as necessary, and make revocation clear in the UI; * be audited before handling user positions. For smart-wallet or EIP-7702 flows, apply the same principle to session keys, account policies, and delegation payloads. The signed permission should be as narrow as possible: exact action, market, amount, fee cap, recipient, deadline, and chain ID. ## Important limitations [#important-limitations] An origination fee at the integration layer does not make the underlying Morpho market permissioned. Users can still interact with Morpho directly or through another integration and bypass your fee. This pattern monetizes your product flow, not the protocol itself. Use it only when the user receives clear value from your borrow product, such as distribution, onboarding, risk tooling, account abstraction, sponsored gas, or portfolio management. --- # Borrow: Developer Resources Source: https://docs.morpho.org/developers/borrow/resources/all This page is your central hub for all the tools, links, and data sources you need to build on Morpho's Borrow product (Morpho Markets). ## Onchain Data & Contracts [#onchain-data--contracts] These resources are for interacting directly with the blockchain. * **Market Parameters**: Every Morpho Market is defined by 5 immutable parameters. The easiest way to fetch these for all markets is via the [Morpho API](/developers/borrow/tutorials/get-data#get-data). * **Contract Specifications**: For detailed function signatures, events, and errors, refer to the canonical contract specs: * [Morpho Core Contract](/get-started/resources/contracts/morpho/) * [Bundlers](/get-started/resources/contracts/bundlers/) * **GitHub Repositories**: * [`morpho-blue`](https://github.com/morpho-org/morpho-blue): The core protocol contracts. ## Offchain Data & Tools [#offchain-data--tools] These tools provide easier access to onchain data for frontends, backends, and analytics. * **Morpho API (Recommended)**: The simplest way to fetch comprehensive data about markets, their states, APYs, and user positions. * **API Playground**: [api.morpho.org/graphql](https://api.morpho.org/graphql) * **Tutorial**: [Using the API for Markets](/developers/borrow/tutorials/get-data#get-data) * **SDKs**: A suite of TypeScript libraries to accelerate your development. * **`@morpho-org/morpho-sdk`**: The default abstraction layer - ready-to-send transactions, plus core entity classes (`@morpho-org/morpho-sdk/entities`) and Viem-based fetchers (`@morpho-org/morpho-sdk/fetch`) for easy onchain data retrieval. * **Vault V2 Public Allocator**: Use the [`@morpho-org/morpho-sdk`](https://www.npmjs.com/package/@morpho-org/morpho-sdk), the [Vault V2 guide](/developers/borrow/tutorials/public-allocator/), and the [Blue Public Allocator source](https://github.com/morpho-org/vault-v2/tree/main/src/periphery/blue-public-allocator). * **Learn more**: [Introduction to Morpho SDKs](/developers/sdks/get-started/) ## Code & Examples [#code--examples] * **Solidity Snippets**: The [`morpho-blue-snippets`](https://github.com/morpho-org/morpho-blue-snippets) repository contains simple, educational Solidity contracts for common market interactions. * **Liquidation Bots**: For advanced developers, the community has provided open-source liquidation bot examples. * **Learn more**: [Community Liquidations Resources](/developers/ecosystem/liquidation-bots/) --- # App Guide Source: https://docs.morpho.org/developers/borrow/tutorials/app-guide --- # Supply Collateral, Borrow, Repay & Withdraw Collateral Source: https://docs.morpho.org/developers/borrow/tutorials/assets-flow {/* AGENT-GENERATED from assets-flow.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} This tutorial provides a comprehensive guide for developers on how to integrate the core functionalities of Morpho Markets: supplying collateral, borrowing, repaying debt, and withdrawing collateral. Unlike ERC4626 vaults, Morpho Markets have a unique interface defined by the core `Morpho` contract. Understanding these functions is essential for building any application with borrowing capabilities. The recommended integration path is the [**Morpho SDK**](/developers/sdks/morpho-sdk/) ([`@morpho-org/morpho-sdk`](https://github.com/morpho-org/sdks/tree/main/packages/morpho-sdk)) - a thin abstraction layer over Morpho Blue. It produces ready-to-send `viem` transactions (atomic supply+borrow, atomic repay+withdraw, partial vs full repay by shares, GeneralAdapter1 authorization, slippage protection, the LLTV buffer) so you don't have to wire `writeContract` calls by hand. ## Key Concepts: Assets vs. Shares [#key-concepts-assets-vs-shares] The core accounting system in Morpho Markets revolves around the concepts of `assets` and `shares`. This applies to the `borrow` positions (the loanable asset), but **not** to the collateral. | Concept | Functions Affected | Description | | ---------- | ------------------ | ------------------------------------------------------------------------------------------------- | | **Assets** | `borrow`, `repay` | The actual underlying token (e.g., USDC, WETH) that a user wants to lend or borrow. | | **Shares** | `borrow`, `repay` | Internal accounting units representing a proportional claim on the market's total supply or debt. | **Best Practice:** * For `borrow`, specifying an exact asset amount is the most common and intuitive approach for users. * For **full repayments**, repaying by shares is highly recommended. Repaying a user's exact borrow-share balance ensures the debt is fully cleared, avoiding "dust" amounts left over from rounding. * Collateral (`supplyCollateral` and `withdrawCollateral`) is always handled in `assets`. ## Prerequisites [#prerequisites] Before you begin, you will need: * The `MarketParams` for the market you want to interact with. You can find active markets using the [Morpho API](/developers/borrow/tutorials/get-data#markets-parameters). * An account with a balance of the collateral asset (e.g., wstETH) and the loan asset (e.g., WETH for repayments). ## Integrate with the Morpho SDK [#integrate-with-the-morpho-sdk] For an end-to-end walkthrough of every market action this SDK exposes (supply collateral, borrow, atomic supply+borrow, repay by assets vs shares, withdraw collateral, atomic repay+withdraw, shared-liquidity reallocations) see the [**Blue subpage**](/developers/sdks/morpho-sdk/blue/); the [Morpho SDK page](/developers/sdks/morpho-sdk/) covers the `getRequirements` flow, the builder = signer invariant, and error classes. For shared-liquidity implementation, use the [Vault V2 Public Allocator guide](/developers/borrow/tutorials/public-allocator/). ### Step 1: Install and set up the client [#step-1-install-and-set-up-the-client] ```bash npm install @morpho-org/morpho-sdk@5.8.0 viem@^2.0.0 # or pnpm add @morpho-org/morpho-sdk@5.8.0 viem@^2.0.0 # or yarn add @morpho-org/morpho-sdk@5.8.0 viem@^2.0.0 ``` ```typescript import { createWalletClient, http, parseUnits } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { mainnet } from "viem/chains"; import { morphoViemExtension } from "@morpho-org/morpho-sdk"; import { MarketParams } from "@morpho-org/morpho-sdk/entities"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const client = createWalletClient({ account, chain: mainnet, transport: http(process.env.RPC_URL_MAINNET), }).extend(morphoViemExtension({ supportSignature: true })); const market = client.morpho.blue( new MarketParams({ loanToken: "0xLoanToken...", collateralToken: "0xCollateralToken...", oracle: "0xOracle...", irm: "0xIrm...", lltv: 860000000000000000n, // 86% }), mainnet.id, ); ``` ### Step 2: Supply collateral & borrow (atomic) [#step-2-supply-collateral--borrow-atomic] The SDK builds an atomic Bundler3 bundle that supplies collateral, then borrows, with an LLTV buffer that prevents the new position from being instantly liquidatable. `getRequirements()` returns the ERC-20 approval (or Permit/Permit2 signature) for the collateral token, plus the one-time `morpho.setAuthorization(generalAdapter1, true)` if it has not been done yet. ```typescript const positionData = await market.getPositionData(account.address); const supplyCollateralBorrow = market.supplyCollateralBorrow({ amount: parseUnits("1.0", 18), // 1 wstETH borrowAmount: parseUnits("2000", 18), // 2000 WETH userAddress: account.address, positionData, }); const signatures = []; for (const req of await supplyCollateralBorrow.getRequirements()) { if ("sign" in req) { signatures.push(await req.sign(client, account.address)); } else { await client.sendTransaction(req); // approval / setAuthorization } } const txHash = await client.sendTransaction(supplyCollateralBorrow.buildTx(signatures)); console.log("Supply + borrow:", txHash); ``` ### Step 3: Repay & withdraw collateral (atomic) [#step-3-repay--withdraw-collateral-atomic] For full repayments, pass the position's fresh borrow-share balance as the repay input instead of an exact asset amount. This guarantees the full debt is cleared regardless of interest accrued between quote and inclusion (no dust). Bundle order is encoded for you: repay first, then withdraw. ```typescript const fresh = await market.getPositionData(account.address); const repayWithdrawCollateral = market.repayWithdrawCollateral({ shares: fresh.borrowShares, // full repay (use { amount } for partial) withdrawAmount: parseUnits("1.0", 18), // 1 wstETH userAddress: account.address, positionData: fresh, }); const signatures = []; for (const req of await repayWithdrawCollateral.getRequirements()) { if ("sign" in req) { signatures.push(await req.sign(client, account.address)); } else { await client.sendTransaction(req); // loan-token approval / setAuthorization } } const txHash = await client.sendTransaction(repayWithdrawCollateral.buildTx(signatures)); console.log("Repay + withdraw:", txHash); ``` Need to move an existing borrower from one Morpho Blue market to another with the same loan and collateral tokens? Use [`MarketV1.refinance()`](/developers/sdks/morpho-sdk/blue/#refinance-to-another-morpho-blue-market) to migrate collateral and debt atomically, with optional shared liquidity from the [Vault V2 Public Allocator](/developers/borrow/tutorials/public-allocator/) on the target market. For separate, non-atomic flows (e.g. `supplyCollateral` only, `borrow` only, `repay` only, `withdrawCollateral` only), see the equivalent action documented on the Morpho SDK's [Blue](/developers/sdks/morpho-sdk/blue/) subpage. --- # Get Data Source: https://docs.morpho.org/developers/borrow/tutorials/get-data ## Before Starting [#before-starting] In this tutorial, you'll see three main ways to fetch data for Morpho Markets: * **API**: Using the Morpho public API. This is the easiest and most direct way to get comprehensive, indexed data for most applications. * **Smart Contract**: Fetching data directly onchain using read functions (or offchain via libraries like Viem). This is best for real-time, trustless data needed within other smart contracts or sensitive applications. * **SDK**: Using the Morpho SDKs for pre-built abstractions that handle complex calculations (like interest accrual) and simplify development. API Endpoint: [api.morpho.org/graphql](https://api.morpho.org/graphql) For each topic below, you'll find guides for each method where applicable. This structure helps you choose the best approach for your specific use case. ## Discovery and Listing [#discovery-and-listing] ### Markets List [#markets-list] Quickly retrieve a list of all markets or filter for specific ones, like those listed for incentives.
*** ```graphql title="All Markets" query { markets { items { marketId lltv oracle { address } irmAddress loanAsset { address symbol decimals } collateralAsset { address symbol decimals } state { borrowAssets supplyAssets fee utilization } } } } ```
*** ```graphql title="Listed Markets" query { markets(where: { listed: true }) { items { marketId listed lltv oracle { address } irmAddress loanAsset { address symbol decimals } collateralAsset { address symbol decimals } state { borrowAssets supplyAssets fee utilization } } } } ```
This example demonstrates how to discover new markets by listening to `CreateMarket` events from the Morpho V1 contract. The script shows how to: * Monitor market creation events within a specific block range * Parse market parameters including loan/collateral tokens, oracle, IRM, and LLTV * Extract and format market IDs and configuration details * Display results in both detailed and table formats for easy analysis * Handle LLTV conversion from WAD format to percentage ```typescript import "dotenv/config"; import { createPublicClient, http, PublicClient, parseAbiItem, Address } from "viem"; import { mainnet } from "viem/chains"; export async function createMainnetClient(): Promise { const client = createPublicClient({ chain: mainnet, transport: http(process.env.RPC_URL_MAINNET!, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); return client; } // Morpho V1 contract address const MORPHO_BLUE_ADDRESS: Address = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // Block range to search for market creations const START_BLOCK = 22867292n; const END_BLOCK = 22867298n; // CreateMarket event ABI const CREATE_MARKET_EVENT = parseAbiItem( "event CreateMarket(bytes32 indexed id, (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams)" ); interface MarketParams { loanToken: Address; collateralToken: Address; oracle: Address; irm: Address; lltv: bigint; } interface MarketCreationEvent { id: `0x${string}`; marketParams: MarketParams; blockNumber: bigint; transactionHash: string; lltvPercentage: number; } /** * Converts LLTV from WAD format to percentage */ function formatLLTV(lltv: bigint): number { return Number(lltv) / 1e18 * 100; } /** * Formats an address to a shorter display format */ function formatAddress(address: string): string { return `${address.slice(0, 6)}...${address.slice(-4)}`; } export async function fetchNewMarketCreations( client: PublicClient, startBlock: bigint = START_BLOCK, endBlock: bigint = END_BLOCK ): Promise { console.log(`Fetching market creations from block ${startBlock} to ${endBlock}...`); // Get logs for CreateMarket events const logs = await client.getLogs({ address: MORPHO_BLUE_ADDRESS, event: CREATE_MARKET_EVENT, fromBlock: startBlock, toBlock: endBlock, }); console.log(`Found ${logs.length} market creation(s)`); // Parse and format the logs const marketCreations: MarketCreationEvent[] = logs.map((log) => { const marketParams = log.args.marketParams!; return { id: log.args.id!, marketParams: { loanToken: marketParams.loanToken, collateralToken: marketParams.collateralToken, oracle: marketParams.oracle, irm: marketParams.irm, lltv: marketParams.lltv, }, blockNumber: log.blockNumber!, transactionHash: log.transactionHash!, lltvPercentage: formatLLTV(marketParams.lltv), }; }); return marketCreations; } export function displayMarketCreations(marketCreations: MarketCreationEvent[]): void { if (marketCreations.length === 0) { console.log("No new markets were created in the specified block range."); return; } console.log("\n✅ --- New Morpho Markets Created --- ✅"); console.log("\n--- Market Summary ---"); console.log(`Total Markets Created: ${marketCreations.length}`); console.log("\n--- Market Details ---"); marketCreations.forEach((market, index) => { console.log(`\n${index + 1}. Market ID: ${market.id}`); console.log(` Loan Token: ${market.marketParams.loanToken}`); console.log(` Collateral Token: ${market.marketParams.collateralToken}`); console.log(` Oracle: ${formatAddress(market.marketParams.oracle)}`); console.log(` IRM: ${formatAddress(market.marketParams.irm)}`); console.log(` LLTV: ${market.lltvPercentage.toFixed(2)}%`); console.log(` Block: ${market.blockNumber}`); console.log(` Transaction: ${market.transactionHash}`); }); // Display in table format for better overview console.log("\n--- Markets Table ---"); console.log("ID | Loan Token | Collateral | LLTV | Block"); console.log("-------------------------------------------------------------------+---------------+--------------+----------+----------"); marketCreations.forEach((market) => { const shortId = `${market.id.slice(0, 8)}...${market.id.slice(-8)}`; const loanToken = formatAddress(market.marketParams.loanToken); const collateralToken = formatAddress(market.marketParams.collateralToken); const lltv = `${market.lltvPercentage.toFixed(2)}%`.padStart(8); console.log( `${shortId.padEnd(66)} | ${loanToken.padEnd(13)} | ${collateralToken.padEnd(12)} | ${lltv} | ${market.blockNumber}` ); }); console.log("\n----------------------------------------"); } export async function main(): Promise { try { const client = await createMainnetClient(); // Fetch market creations in the specified block range const marketCreations = await fetchNewMarketCreations(client, START_BLOCK, END_BLOCK); // Display the results displayMarketCreations(marketCreations); } catch (error) { console.error("Error fetching market creations:", error); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Fetching market creations from block 22867292 to 22867298... Found 1 market creation(s) ✅ --- New Morpho Markets Created --- ✅ --- Market Summary --- Total Markets Created: 1 --- Market Details --- 1. Market ID: 0x9a33209eee9e93f5f7aed04085f9f5e0ce9a7a103c476f5c30a0e5ca03c3d540 Loan Token: 0xdAC17F958D2ee523a2206206994597C13D831ec7 Collateral Token: 0x8ddac7aa85Ce324AF75a3bFcB876375555d43BB8 Oracle: 0xF470...66e0 IRM: 0x870a...00BC LLTV: 91.50% Block: 22867295 Transaction: 0x4c9e1efb7e86f8bb15c01f3f109d88ec0a0e0d553140628dfc9d852dcc902d51 --- Markets Table --- ID | Loan Token | Collateral | LLTV | Block -------------------------------------------------------------------+---------------+--------------+----------+---------- 0x9a3320...03c3d540 | 0xdAC1...1ec7 | 0x8dda...3BB8 | 91.50% | 22867295 ---------------------------------------- ``` This example demonstrates real-time market discovery by monitoring blockchain events, which is essential for tracking new lending opportunities and market configurations as they're created. You can discover markets by listening for the `CreateMarket` event emitted from the core `Morpho` contract: ```solidity // Listen to CreateMarket events event CreateMarket( bytes32 indexed id, MarketParams marketParams ); // Market parameters structure struct MarketParams { address loanToken; address collateralToken; address oracle; address irm; uint256 lltv; } // Get market details by ID function idToMarketParams(bytes32 id) external view returns (MarketParams memory); // Check if market exists function market(bytes32 id) external view returns ( uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee ); ``` **Key Functions:** * **Event Monitoring**: Listen to `CreateMarket` events for new markets * **Market Resolution**: `idToMarketParams(id)` returns full market configuration * **Market State**: `market(id)` returns current market liquidity and state * **Market ID**: Computed as `keccak256(abi.encode(marketParams))` **Event Logs**: See the [Etherscan events log](https://etherscan.io/address/0xbbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcb#events) for real examples.
### Market Parameters [#market-parameters] Fetch the core, immutable parameters for one or more markets.
*** ```graphql title="All Markets" query { markets( first: 100 orderBy: SupplyAssetsUsd orderDirection: Desc where: { chainId_in: [1, 8453] } ) { items { marketId loanAsset { address } collateralAsset { address } lltv irmAddress oracle { address } } } } ```
*** ```graphql title="Unique Market" query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { marketId loanAsset { address } collateralAsset { address } lltv irmAddress oracle { address } } } ```
This example demonstrates comprehensive market parameter operations including: * Decoding market IDs to parameters using `idToMarketParams` * Encoding market parameters to generate market IDs using keccak256 * Validating market ID integrity by round-trip encoding/decoding * Checking market existence and fetching token information * Understanding the relationship between market parameters and their unique identifiers ```typescript import "dotenv/config"; import { createPublicClient, http, PublicClient, parseAbi, Address, keccak256, encodeAbiParameters, parseAbiParameters, formatUnits, } from "viem"; import { mainnet } from "viem/chains"; export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // Morpho V1 contract address const MORPHO_BLUE_ADDRESS: Address = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // Example market ID to demonstrate const EXAMPLE_MARKET_ID: `0x${string}` = "0x9a33209eee9e93f5f7aed04085f9f5e0ce9a7a103c476f5c30a0e5ca03c3d540"; // Minimal Morpho V1 ABI for market parameters const MORPHO_BLUE_ABI = parseAbi([ "function idToMarketParams(bytes32 id) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", "function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)", ]); // ERC20 ABI for token information const ERC20_ABI = parseAbi([ "function symbol() view returns (string)", "function name() view returns (string)", "function decimals() view returns (uint8)", ]); interface MarketParams { loanToken: Address; collateralToken: Address; oracle: Address; irm: Address; lltv: bigint; } interface TokenInfo { address: Address; symbol: string; name: string; decimals: number; } interface MarketInfo { id: `0x${string}`; params: MarketParams; loanToken: TokenInfo; collateralToken: TokenInfo; lltvPercentage: number; exists: boolean; } interface MarketState { totalSupplyAssets: bigint; totalSupplyShares: bigint; totalBorrowAssets: bigint; totalBorrowShares: bigint; lastUpdate: bigint; fee: bigint; } /** * Converts LLTV from WAD format to percentage */ function formatLLTV(lltv: bigint): number { return Number(formatUnits(lltv, 18)) * 100; } /** * Fetches token information for a given address */ async function getTokenInfo( client: PublicClient, tokenAddress: Address ): Promise { try { const [symbol, name, decimals] = await client.multicall({ contracts: [ { address: tokenAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "name" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "decimals" }, ], allowFailure: false, }); return { address: tokenAddress, symbol, name, decimals, }; } catch (error) { // If token info fails, return address as fallback return { address: tokenAddress, symbol: `Token(${tokenAddress.slice(0, 6)}...)`, name: `Unknown Token ${tokenAddress.slice(0, 6)}...`, decimals: 18, // Default fallback }; } } /** * Checks if a market exists by verifying if any of its state values are non-zero */ async function checkMarketExists( client: PublicClient, marketId: `0x${string}` ): Promise { try { const marketState = await client.readContract({ address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "market", args: [marketId], }) as [bigint, bigint, bigint, bigint, bigint, bigint]; // Market exists if it has been created (any non-zero state indicates creation) return marketState.some(value => value > 0n); } catch (error) { return false; } } /** * Encodes market parameters into a market ID using keccak256 hash */ export function encodeMarketId(marketParams: MarketParams): `0x${string}` { const encoded = encodeAbiParameters( parseAbiParameters("address, address, address, address, uint256"), [ marketParams.loanToken, marketParams.collateralToken, marketParams.oracle, marketParams.irm, marketParams.lltv, ] ); return keccak256(encoded); } /** * Decodes a market ID back to market parameters by querying the Morpho V1 contract */ export async function decodeMarketId( client: PublicClient, marketId: `0x${string}` ): Promise { try { const [loanToken, collateralToken, oracle, irm, lltv] = await client.readContract({ address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "idToMarketParams", args: [marketId], }) as [Address, Address, Address, Address, bigint]; // Check if market actually exists (all zero values indicate non-existent market) if (loanToken === "0x0000000000000000000000000000000000000000") { return null; } return { loanToken, collateralToken, oracle, irm, lltv, }; } catch (error) { console.error(`Error decoding market ID ${marketId}:`, error); return null; } } /** * Validates that encoding market parameters produces the expected market ID */ export async function validateMarketId( client: PublicClient, marketId: `0x${string}` ): Promise<{ isValid: boolean; params: MarketParams | null; computedId: `0x${string}` | null }> { const params = await decodeMarketId(client, marketId); if (!params) { return { isValid: false, params: null, computedId: null }; } const computedId = encodeMarketId(params); const isValid = computedId.toLowerCase() === marketId.toLowerCase(); return { isValid, params, computedId }; } /** * Retrieves comprehensive market information including parameters and token details */ export async function getMarketInfo( client: PublicClient, marketId: `0x${string}` ): Promise { console.log(`Fetching market information for ID: ${marketId}`); const params = await decodeMarketId(client, marketId); if (!params) { return { id: marketId, params: { loanToken: "0x0000000000000000000000000000000000000000", collateralToken: "0x0000000000000000000000000000000000000000", oracle: "0x0000000000000000000000000000000000000000", irm: "0x0000000000000000000000000000000000000000", lltv: 0n, }, loanToken: { address: "0x0000000000000000000000000000000000000000", symbol: "UNKNOWN", name: "Unknown Token", decimals: 0, }, collateralToken: { address: "0x0000000000000000000000000000000000000000", symbol: "UNKNOWN", name: "Unknown Token", decimals: 0, }, lltvPercentage: 0, exists: false, }; } // Check if market exists const exists = await checkMarketExists(client, marketId); // Fetch token information const [loanTokenInfo, collateralTokenInfo] = await Promise.all([ getTokenInfo(client, params.loanToken), getTokenInfo(client, params.collateralToken), ]); return { id: marketId, params, loanToken: loanTokenInfo, collateralToken: collateralTokenInfo, lltvPercentage: formatLLTV(params.lltv), exists, }; } /** * Demonstrates market ID encoding and validation */ export async function demonstrateMarketIdOperations( client: PublicClient, marketId: `0x${string}` ): Promise { console.log("=== Market ID Operations Demo ===\n"); // 1. Decode market ID to parameters console.log("1. Decoding market ID to parameters..."); const params = await decodeMarketId(client, marketId); if (!params) { console.log("❌ Market does not exist or failed to decode"); return; } console.log("✅ Market parameters decoded successfully:"); console.log(` Loan Token: ${params.loanToken}`); console.log(` Collateral Token: ${params.collateralToken}`); console.log(` Oracle: ${params.oracle}`); console.log(` IRM: ${params.irm}`); console.log(` LLTV: ${formatLLTV(params.lltv)}%`); // 2. Re-encode parameters to market ID console.log("\n2. Re-encoding parameters to market ID..."); const recomputedId = encodeMarketId(params); console.log(` Original ID: ${marketId}`); console.log(` Computed ID: ${recomputedId}`); // 3. Validate the match console.log("\n3. Validation result:"); const isMatch = recomputedId.toLowerCase() === marketId.toLowerCase(); console.log(` ${isMatch ? "✅" : "❌"} Market ID validation: ${isMatch ? "PASS" : "FAIL"}`); // 4. Check if market exists onchain console.log("\n4. Checking market existence..."); const exists = await checkMarketExists(client, marketId); console.log(` ${exists ? "✅" : "❌"} Market exists onchain: ${exists ? "YES" : "NO"}`); } /** * Displays comprehensive market information */ export function displayMarketInfo(marketInfo: MarketInfo): void { console.log("\n✅ --- Morpho Market Parameters --- ✅"); console.log(`\n--- Market Overview ---`); console.log(`Market ID: ${marketInfo.id}`); console.log(`Exists: ${marketInfo.exists ? "✅ Yes" : "❌ No"}`); if (!marketInfo.exists) { console.log("This market does not exist on Morpho V1."); return; } console.log(`\n--- Market Parameters ---`); console.log(`Loan Token: ${marketInfo.loanToken.name} (${marketInfo.loanToken.symbol})`); console.log(` Address: ${marketInfo.params.loanToken}`); console.log(` Decimals: ${marketInfo.loanToken.decimals}`); console.log(`Collateral Token: ${marketInfo.collateralToken.name} (${marketInfo.collateralToken.symbol})`); console.log(` Address: ${marketInfo.params.collateralToken}`); console.log(` Decimals: ${marketInfo.collateralToken.decimals}`); console.log(`Oracle: ${marketInfo.params.oracle}`); console.log(`IRM (Interest Rate Model): ${marketInfo.params.irm}`); console.log(`LLTV (Loan-to-Value): ${marketInfo.lltvPercentage.toFixed(2)}%`); console.log("\n--- Encoded Parameters ---"); console.log(`LLTV (Raw): ${marketInfo.params.lltv.toString()}`); console.log(`Market ID Validation: ${encodeMarketId(marketInfo.params) === marketInfo.id ? "✅ Valid" : "❌ Invalid"}`); console.log("\n----------------------------------------"); } /** * Main function to demonstrate market parameter operations */ export async function main(): Promise { try { const client = createMainnetClient(); // Demonstrate market ID operations await demonstrateMarketIdOperations(client, EXAMPLE_MARKET_ID); // Get comprehensive market information const marketInfo = await getMarketInfo(client, EXAMPLE_MARKET_ID); // Display results displayMarketInfo(marketInfo); } catch (error) { console.error("Error in market parameters script:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` === Market ID Operations Demo === 1. Decoding market ID to parameters... ✅ Market parameters decoded successfully: Loan Token: 0xdAC17F958D2ee523a2206206994597C13D831ec7 Collateral Token: 0x8ddac7aa85Ce324AF75a3bFcB876375555d43BB8 Oracle: 0xF47020f01e77257Fe86B9ECb36552486E0Ae66e0 IRM: 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC LLTV: 91.5% 2. Re-encoding parameters to market ID... Original ID: 0x9a33209eee9e93f5f7aed04085f9f5e0ce9a7a103c476f5c30a0e5ca03c3d540 Computed ID: 0x9a33209eee9e93f5f7aed04085f9f5e0ce9a7a103c476f5c30a0e5ca03c3d540 3. Validation result: ✅ Market ID validation: PASS 4. Checking market existence... ✅ Market exists onchain: YES Fetching market information for ID: 0x9a33209eee9e93f5f7aed04085f9f5e0ce9a7a103c476f5c30a0e5ca03c3d540 ✅ --- Morpho Market Parameters --- ✅ --- Market Overview --- Market ID: 0x9a33209eee9e93f5f7aed04085f9f5e0ce9a7a103c476f5c30a0e5ca03c3d540 Exists: ✅ Yes --- Market Parameters --- Loan Token: Tether USD (USDT) Address: 0xdAC17F958D2ee523a2206206994597C13D831ec7 Decimals: 6 Collateral Token: Pendle Market Wrapped (PENDLE-LPT-WRAPPED) Address: 0x8ddac7aa85Ce324AF75a3bFcB876375555d43BB8 Decimals: 18 Oracle: 0xF47020f01e77257Fe86B9ECb36552486E0Ae66e0 IRM (Interest Rate Model): 0x870aC11D48B15DB9a138Cf899d20F13F79Ba00BC LLTV (Loan-to-Value): 91.50% --- Encoded Parameters --- LLTV (Raw): 915000000000000000 Market ID Validation: ✅ Valid ---------------------------------------- ``` This example demonstrates the bidirectional relationship between market parameters and market IDs, essential for understanding how Morpho Markets are uniquely identified and accessed. You can retrieve market parameters by using the `idToMarketParams` view function on the `Morpho` contract, passing the `marketId`. ```solidity // Get market parameters from market ID ( address loanToken, address collateralToken, address oracle, address irm, uint256 lltv ) = IMorpho(morpho).idToMarketParams(marketId); // Generate market ID from parameters struct MarketParams { address loanToken; address collateralToken; address oracle; address irm; uint256 lltv; } // Market ID is the keccak256 hash of encoded parameters bytes32 marketId = keccak256(abi.encode(marketParams)); // Check if market exists ( uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee ) = IMorpho(morpho).market(marketId); // Market exists if any state value > 0 bool marketExists = totalSupplyAssets > 0 || totalSupplyShares > 0 || totalBorrowAssets > 0 || totalBorrowShares > 0; ``` **Key Functions:** * **Parameter Lookup**: `idToMarketParams(marketId)` returns all market configuration * **Market ID Generation**: `keccak256(abi.encode(marketParams))` creates unique identifier * **Existence Check**: `market(marketId)` returns state (non-zero values indicate existence) * **Parameter Validation**: Re-encode parameters to verify market ID integrity **Important Notes:** * Market IDs are deterministic based on parameters * Non-existent markets return zero values from `idToMarketParams` * LLTV is stored in WAD format (18 decimals)
## Market Metrics [#market-metrics] ### Total Collateral, Borrow & Supply [#total-collateral-borrow--supply] Get the real-time state of liquidity and debt in a market.
*** ```graphql title="All Markets" query { markets( first: 100 orderBy: SupplyAssetsUsd orderDirection: Desc where: { chainId_in: [1, 8453] } ) { items { marketId state { collateralAssets collateralAssetsUsd borrowAssets borrowAssetsUsd supplyAssets supplyAssetsUsd liquidityAssets liquidityAssetsUsd } } } } ```
*** ```graphql title="Unique Market" query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { state { collateralAssets borrowAssets supplyAssets liquidityAssets } } } ```
This example demonstrates comprehensive market liquidity analysis including: * Real-time interest accrual for supply and borrow assets * Collateral tracking (which doesn't accrue interest) * Market utilization and available liquidity calculations * Comparison between stale (last update) and current (accrued) values * Efficient batch processing for multiple markets * Important distinction: only loan token amounts (supply/borrow) accrue interest, collateral remains static The `market()` view function returns values that **do not include** interest accrued since the last onchain interaction. For accurate, up-to-the-second data, you must use the SDK or manually calculate the accrued interest. ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // Morpho V1 contract address const MORPHO_BLUE_ADDRESS: Address = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // Example market ID for demonstration const EXAMPLE_MARKET_ID: `0x${string}` = "0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64"; // Morpho V1 ABI for market operations const MORPHO_BLUE_ABI = parseAbi([ "function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)", "function idToMarketParams(bytes32 id) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", "function position(bytes32 id, address user) view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)", ]); // Interest Rate Model ABI const IRM_ABI = parseAbi([ "function borrowRateView((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee) market) view returns (uint256)", ]); // ERC20 ABI for token information const ERC20_ABI = parseAbi([ "function symbol() view returns (string)", "function name() view returns (string)", "function decimals() view returns (uint8)", "function totalSupply() view returns (uint256)", ]); type MarketParams = { readonly loanToken: Address; readonly collateralToken: Address; readonly oracle: Address; readonly irm: Address; readonly lltv: bigint; }; type MarketState = { readonly totalSupplyAssets: bigint; readonly totalSupplyShares: bigint; readonly totalBorrowAssets: bigint; readonly totalBorrowShares: bigint; readonly lastUpdate: bigint; readonly fee: bigint; }; interface TokenInfo { address: Address; symbol: string; name: string; decimals: number; } interface MarketLiquidityInfo { marketId: `0x${string}`; params: MarketParams; staleState: MarketState; currentState: MarketState; loanToken: TokenInfo; collateralToken: TokenInfo; lltvPercentage: number; // Current (accrued) values totalSupplyAssets: bigint; totalBorrowAssets: bigint; totalCollateralAssets: bigint; // Formatted values formattedTotalSupply: string; formattedTotalBorrow: string; formattedTotalCollateral: string; // Metrics utilization: bigint; utilizationPercentage: number; availableLiquidity: bigint; formattedAvailableLiquidity: string; interestAccrued: bigint; formattedInterestAccrued: string; lastUpdateTime: Date; blockTimestamp: bigint; } const WAD = 10n ** 18n; const wMulDown = (x: bigint, y: bigint): bigint => (x * y) / WAD; const wDivUp = (x: bigint, y: bigint): bigint => (x * WAD + y - 1n) / y; const wTaylorCompounded = (x: bigint, n: bigint): bigint => { const firstTerm = x * n; const secondTerm = (firstTerm * firstTerm) / (2n * WAD); const thirdTerm = (secondTerm * firstTerm) / (3n * WAD); return firstTerm + secondTerm + thirdTerm; }; /** * Formats LLTV from WAD format to percentage */ function formatLLTV(lltv: bigint): number { return Number(formatUnits(lltv, 18)) * 100; } /** * Formats utilization from WAD format to percentage */ function formatUtilization(utilization: bigint): number { return Number(formatUnits(utilization, 16)); // 18 - 2 = 16 for percentage } /** * Fetches token information for a given address */ async function getTokenInfo( client: PublicClient, tokenAddress: Address ): Promise { try { const [symbol, name, decimals] = await client.multicall({ contracts: [ { address: tokenAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "name" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "decimals" }, ], allowFailure: false, }); return { address: tokenAddress, symbol, name, decimals, }; } catch (error) { return { address: tokenAddress, symbol: `Token(${tokenAddress.slice(0, 6)}...)`, name: `Unknown Token ${tokenAddress.slice(0, 6)}...`, decimals: 18, }; } } /** * Accrues interest on a market from its last update time to the current block's timestamp. * This updates the total supply and borrow assets but NOT the collateral (which doesn't accrue interest). */ function accrueInterests( marketState: MarketState, borrowRate: bigint, blockTimestamp: bigint ): { accruedState: MarketState; interestAccrued: bigint } { const elapsed = blockTimestamp - marketState.lastUpdate; if (elapsed === 0n || marketState.totalBorrowAssets === 0n) { return { accruedState: marketState, interestAccrued: 0n }; } const interest = wMulDown( marketState.totalBorrowAssets, wTaylorCompounded(borrowRate, elapsed) ); const accruedState = { ...marketState, totalSupplyAssets: marketState.totalSupplyAssets + interest, totalBorrowAssets: marketState.totalBorrowAssets + interest, lastUpdate: blockTimestamp, }; return { accruedState, interestAccrued: interest }; } /** * Calculates total collateral across all positions in a market. * Collateral doesn't accrue interest, so we can sum it directly. */ async function getTotalCollateralAssets( client: PublicClient, marketId: `0x${string}` ): Promise { // Note: This is a simplified approach. In practice, you'd need to: // 1. Track all position holders (via events or an indexer) // 2. Sum their collateral amounts // For demonstration, we'll use a placeholder approach // This would require tracking all users who have positions in this market // For now, we'll return 0n as a placeholder // In a real implementation, you'd: // - Listen to SupplyCollateral events // - Track all unique addresses // - Sum their current collateral positions console.log("Note: Total collateral calculation requires tracking all position holders"); return 0n; } /** * Retrieves comprehensive market liquidity information including real-time accrued values. */ export async function getMarketLiquidityInfo( client: PublicClient, marketId: `0x${string}` ): Promise { console.log(`Fetching liquidity information for market: ${marketId}`); // Get current block timestamp const block = await client.getBlock({ blockTag: "latest" }); const blockTimestamp = block.timestamp; // Get market state and parameters const [marketStateResult, marketParamsResult] = await client.multicall({ contracts: [ { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "market", args: [marketId] }, { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "idToMarketParams", args: [marketId] }, ], allowFailure: false, }); const staleState: MarketState = { totalSupplyAssets: marketStateResult[0], totalSupplyShares: marketStateResult[1], totalBorrowAssets: marketStateResult[2], totalBorrowShares: marketStateResult[3], lastUpdate: marketStateResult[4], fee: marketStateResult[5], }; const params: MarketParams = { loanToken: marketParamsResult[0], collateralToken: marketParamsResult[1], oracle: marketParamsResult[2], irm: marketParamsResult[3], lltv: marketParamsResult[4], }; // Get token information const [loanToken, collateralToken] = await Promise.all([ getTokenInfo(client, params.loanToken), getTokenInfo(client, params.collateralToken), ]); // Initialize values let currentState = staleState; let interestAccrued = 0n; // Calculate current state with interest accrual if IRM is set if (params.irm !== "0x0000000000000000000000000000000000000000") { // Get borrow rate from IRM const borrowRate = await client.readContract({ address: params.irm, abi: IRM_ABI, functionName: "borrowRateView", args: [params, staleState], }) as bigint; // Accrue interest to get current state const accrualResult = accrueInterests(staleState, borrowRate, blockTimestamp); currentState = accrualResult.accruedState; interestAccrued = accrualResult.interestAccrued; } // Get total collateral (doesn't accrue interest) const totalCollateralAssets = await getTotalCollateralAssets(client, marketId); // Calculate metrics const utilization = currentState.totalSupplyAssets > 0n ? wDivUp(currentState.totalBorrowAssets, currentState.totalSupplyAssets) : 0n; const availableLiquidity = currentState.totalSupplyAssets - currentState.totalBorrowAssets; return { marketId, params, staleState, currentState, loanToken, collateralToken, lltvPercentage: formatLLTV(params.lltv), totalSupplyAssets: currentState.totalSupplyAssets, totalBorrowAssets: currentState.totalBorrowAssets, totalCollateralAssets, formattedTotalSupply: formatUnits(currentState.totalSupplyAssets, loanToken.decimals), formattedTotalBorrow: formatUnits(currentState.totalBorrowAssets, loanToken.decimals), formattedTotalCollateral: formatUnits(totalCollateralAssets, collateralToken.decimals), utilization, utilizationPercentage: formatUtilization(utilization), availableLiquidity, formattedAvailableLiquidity: formatUnits(availableLiquidity, loanToken.decimals), interestAccrued, formattedInterestAccrued: formatUnits(interestAccrued, loanToken.decimals), lastUpdateTime: new Date(Number(staleState.lastUpdate) * 1000), blockTimestamp, }; } /** * Retrieves liquidity information for multiple markets efficiently */ export async function getMultipleMarketsLiquidity( client: PublicClient, marketIds: `0x${string}`[] ): Promise { console.log(`Fetching liquidity information for ${marketIds.length} markets...`); const liquidityPromises = marketIds.map(marketId => getMarketLiquidityInfo(client, marketId) ); return Promise.all(liquidityPromises); } /** * Displays comprehensive market liquidity information */ export function displayMarketLiquidity(liquidityInfo: MarketLiquidityInfo): void { console.log("\n✅ --- Morpho Market Liquidity Information --- ✅"); console.log(`\n--- Market Overview ---`); console.log(`Market ID: ${liquidityInfo.marketId}`); console.log(`Loan Token: ${liquidityInfo.loanToken.name} (${liquidityInfo.loanToken.symbol})`); console.log(`Collateral Token: ${liquidityInfo.collateralToken.name} (${liquidityInfo.collateralToken.symbol})`); console.log(`LLTV: ${liquidityInfo.lltvPercentage.toFixed(2)}%`); console.log(`\n--- Current Liquidity (Real-time) ---`); console.log(`Total Supply: ${liquidityInfo.formattedTotalSupply} ${liquidityInfo.loanToken.symbol}`); console.log(`Total Borrow: ${liquidityInfo.formattedTotalBorrow} ${liquidityInfo.loanToken.symbol}`); console.log(`Total Collateral: ${liquidityInfo.formattedTotalCollateral} ${liquidityInfo.collateralToken.symbol}`); console.log(`Available Liquidity: ${liquidityInfo.formattedAvailableLiquidity} ${liquidityInfo.loanToken.symbol}`); console.log(`Utilization: ${liquidityInfo.utilizationPercentage.toFixed(2)}%`); console.log(`\n--- Interest Accrual ---`); console.log(`Last Update: ${liquidityInfo.lastUpdateTime.toISOString()}`); console.log(`Interest Accrued: ${liquidityInfo.formattedInterestAccrued} ${liquidityInfo.loanToken.symbol}`); console.log(`\n--- Stale vs Current Comparison ---`); console.log(`Stale Total Supply: ${formatUnits(liquidityInfo.staleState.totalSupplyAssets, liquidityInfo.loanToken.decimals)} ${liquidityInfo.loanToken.symbol}`); console.log(`Current Total Supply: ${liquidityInfo.formattedTotalSupply} ${liquidityInfo.loanToken.symbol}`); console.log(`Stale Total Borrow: ${formatUnits(liquidityInfo.staleState.totalBorrowAssets, liquidityInfo.loanToken.decimals)} ${liquidityInfo.loanToken.symbol}`); console.log(`Current Total Borrow: ${liquidityInfo.formattedTotalBorrow} ${liquidityInfo.loanToken.symbol}`); console.log(`\n--- Raw Values ---`); console.log(`Total Supply Assets: ${liquidityInfo.totalSupplyAssets}`); console.log(`Total Borrow Assets: ${liquidityInfo.totalBorrowAssets}`); console.log(`Total Collateral Assets: ${liquidityInfo.totalCollateralAssets}`); console.log(`Utilization (WAD): ${liquidityInfo.utilization}`); console.log("\n----------------------------------------"); } /** * Displays a summary table of multiple markets liquidity */ export function displayMarketsLiquiditySummary(markets: MarketLiquidityInfo[]): void { if (markets.length === 0) { console.log("No markets to display."); return; } console.log("\n✅ --- Markets Liquidity Summary --- ✅"); console.log("\nMarket ID | Loan/Collateral | Total Supply | Total Borrow | Utilization | Available"); console.log("-----------------------------+---------------------+--------------+--------------+-------------+----------"); markets.forEach((market) => { const shortId = `${market.marketId.slice(0, 8)}...${market.marketId.slice(-8)}`; const tokenPair = `${market.loanToken.symbol}/${market.collateralToken.symbol}`; const totalSupply = parseFloat(market.formattedTotalSupply).toLocaleString('en-US', { maximumFractionDigits: 0 }); const totalBorrow = parseFloat(market.formattedTotalBorrow).toLocaleString('en-US', { maximumFractionDigits: 0 }); const utilization = `${market.utilizationPercentage.toFixed(1)}%`.padStart(10); const available = parseFloat(market.formattedAvailableLiquidity).toLocaleString('en-US', { maximumFractionDigits: 0 }); console.log( `${shortId.padEnd(28)} | ${tokenPair.padEnd(19)} | ${totalSupply.padStart(12)} | ${totalBorrow.padStart(12)} | ${utilization} | ${available.padStart(8)}` ); }); console.log("\n----------------------------------------"); } /** * Main function to demonstrate market liquidity calculations */ export async function main(): Promise { try { const client = createMainnetClient(); // Get liquidity information for the example market const marketLiquidity = await getMarketLiquidityInfo(client, EXAMPLE_MARKET_ID); // Display detailed results displayMarketLiquidity(marketLiquidity); // Example: Get liquidity for multiple markets const multipleMarkets = [EXAMPLE_MARKET_ID]; const marketsLiquidity = await getMultipleMarketsLiquidity(client, multipleMarkets); // Display summary displayMarketsLiquiditySummary(marketsLiquidity); } catch (error) { console.error("Error in market liquidity calculation:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Fetching liquidity information for market: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 Note: Total collateral calculation requires tracking all position holders ✅ --- Morpho Market Liquidity Information --- ✅ --- Market Overview --- Market ID: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 Loan Token: USD Coin (USDC) Collateral Token: Coinbase Wrapped BTC (cbBTC) LLTV: 86.00% --- Current Liquidity (Real-time) --- Total Supply: 522083875.989369 USDC Total Borrow: 481268818.125629 USDC Total Collateral: 0 cbBTC Available Liquidity: 40815057.86374 USDC Utilization: 92.18% --- Interest Accrual --- Last Update: 2025-11-07T10:57:35.000Z Interest Accrued: 0 USDC --- Stale vs Current Comparison --- Stale Total Supply: 522083875.989369 USDC Current Total Supply: 522083875.989369 USDC Stale Total Borrow: 481268818.125629 USDC Current Total Borrow: 481268818.125629 USDC --- Raw Values --- Total Supply Assets: 522083875989369 Total Borrow Assets: 481268818125629 Total Collateral Assets: 0 Utilization (WAD): 921822795644868562 ---------------------------------------- Fetching liquidity information for 1 markets... Fetching liquidity information for market: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 Note: Total collateral calculation requires tracking all position holders ✅ --- Markets Liquidity Summary --- ✅ Market ID | Loan/Collateral | Total Supply | Total Borrow | Utilization | Available -----------------------------+---------------------+--------------+--------------+-------------+---------- 0x64d65c...883fcc64 | USDC/cbBTC | 522,083,876 | 481,268,818 | 92.2% | 40,815,058 ---------------------------------------- ``` This example shows the crucial difference between stale market data (from last onchain update) and current real-time data (with interest accrued), essential for accurate liquidity analysis. * As it is important to accrue interests on the underlying markets, Morpho Association provided a library to accrue them onchain. Feel free to refer to the [Morpho Market Snippets](https://github.com/morpho-org/morpho-blue-snippets/blob/main/src/morpho-blue/MorphoBlueSnippets.sol) for extensive example. ```solidity /// @notice Calculates the total supply of assets in a specific market. /// @param marketParams The parameters of the market. /// @return totalSupplyAssets The calculated total supply of assets. function marketTotalSupply(MarketParams memory marketParams) public view returns (uint256 totalSupplyAssets) { totalSupplyAssets = morpho.expectedTotalSupplyAssets(marketParams); } /// @notice Calculates the total borrow of assets in a specific market. /// @param marketParams The parameters of the market. /// @return totalBorrowAssets The calculated total borrow of assets. function marketTotalBorrow(MarketParams memory marketParams) public view returns (uint256 totalBorrowAssets) { totalBorrowAssets = morpho.expectedTotalBorrowAssets(marketParams); } ``` The total collateral on a given market is not easily retrievable onchain. One has to index all positions, and sum through the morpho.position(market, user) calls.
### Market APY (Native & Rewards) [#market-apy-native--rewards] Get the real-time APY, interest rates, and accrual information for markets. The API provides both the native APY from borrowing/lending and the additional APR from reward incentives. ###### Important UI Reference one might find on apps: [#important-ui-reference-one-might-find-on-apps] 1. `avgSupplyApy` represents the native supplier APY (average supply APY excluding rewards). 2. `avgNetSupplyApy` represents the supplier APY including rewards. 3. `avgBorrowApy` represents the native borrower APY (average borrow APY excluding rewards). 4. `avgNetBorrowApy` represents the borrower APY including rewards.
*** ```graphql title="All Markets" query { markets( first: 100 orderBy: SupplyAssetsUsd orderDirection: Desc where: { chainId_in: [1, 8453] } ) { items { marketId state { borrowApy avgBorrowApy avgNetBorrowApy supplyApy avgSupplyApy avgNetSupplyApy rewards { asset { address chain { id } } supplyApr borrowApr } } } } } ```
*** ```graphql title="Unique Market" query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { state { borrowApy avgBorrowApy avgNetBorrowApy supplyApy avgSupplyApy avgNetSupplyApy rewards { supplyApr borrowApr } } } } ```
This example demonstrates comprehensive market APY calculations including: * Real-time interest accrual from last update to current block * Borrow and supply APY calculations using Interest Rate Models (IRM) * Market utilization and fee impact on supply rates * Interest compounding using Taylor expansion for precision * Comparison between stale and accrued market states * Efficient batch processing for multiple markets ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // Morpho V1 contract address const MORPHO_BLUE_ADDRESS: Address = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // Example market ID for demonstration const EXAMPLE_MARKET_ID: `0x${string}` = "0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64"; // Morpho V1 ABI for market operations const MORPHO_BLUE_ABI = parseAbi([ "function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)", "function idToMarketParams(bytes32 id) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", ]); // Interest Rate Model ABI const IRM_ABI = parseAbi([ "function borrowRateView((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee) market) view returns (uint256)", ]); // ERC20 ABI for token information const ERC20_ABI = parseAbi([ "function symbol() view returns (string)", "function name() view returns (string)", "function decimals() view returns (uint8)", ]); type MarketParams = { loanToken: Address; collateralToken: Address; oracle: Address; irm: Address; lltv: bigint; } type MarketState = { totalSupplyAssets: bigint; totalSupplyShares: bigint; totalBorrowAssets: bigint; totalBorrowShares: bigint; lastUpdate: bigint; fee: bigint; } interface TokenInfo { address: Address; symbol: string; name: string; decimals: number; } interface MarketAPYInfo { marketId: `0x${string}`; params: MarketParams; state: MarketState; accruedState: MarketState; loanToken: TokenInfo; collateralToken: TokenInfo; lltvPercentage: number; borrowRate: bigint; borrowAPY: bigint; supplyAPY: bigint; utilization: bigint; borrowAPYPercentage: number; supplyAPYPercentage: number; utilizationPercentage: number; lastUpdateTime: Date; interestAccrued: bigint; blockTimestamp: bigint; } const WAD = 10n ** 18n; const SECONDS_PER_YEAR = 31536000n; const wMulDown = (x: bigint, y: bigint): bigint => (x * y) / WAD; const wDivUp = (x: bigint, y: bigint): bigint => (x * WAD + y - 1n) / y; const wTaylorCompounded = (x: bigint, n: bigint): bigint => { const firstTerm = x * n; const secondTerm = (firstTerm * firstTerm) / (2n * WAD); const thirdTerm = (secondTerm * firstTerm) / (3n * WAD); return firstTerm + secondTerm + thirdTerm; }; /** * Formats a WAD-scaled value (18 decimals) to percentage */ function formatWADToPercentage(value: bigint): number { return Number(formatUnits(value, 16)); // 18 - 2 = 16 for percentage } /** * Formats LLTV from WAD format to percentage */ function formatLLTV(lltv: bigint): number { return Number(formatUnits(lltv, 18)) * 100; } /** * Fetches token information for a given address */ async function getTokenInfo( client: PublicClient, tokenAddress: Address ): Promise { try { const [symbol, name, decimals] = await client.multicall({ contracts: [ { address: tokenAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "name" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "decimals" }, ], allowFailure: false, }); return { address: tokenAddress, symbol, name, decimals, }; } catch (error) { return { address: tokenAddress, symbol: `Token(${tokenAddress.slice(0, 6)}...)`, name: `Unknown Token ${tokenAddress.slice(0, 6)}...`, decimals: 18, }; } } /** * Accrues interest on a market from its last update time to the current block's timestamp. * This is crucial for getting the real-time state of the market. */ function accrueInterests( marketState: MarketState, borrowRate: bigint, blockTimestamp: bigint ): { accruedState: MarketState; interestAccrued: bigint } { const elapsed = blockTimestamp - marketState.lastUpdate; if (elapsed === 0n || marketState.totalBorrowAssets === 0n) { return { accruedState: marketState, interestAccrued: 0n }; } const interest = wMulDown( marketState.totalBorrowAssets, wTaylorCompounded(borrowRate, elapsed) ); const accruedState = { ...marketState, totalSupplyAssets: marketState.totalSupplyAssets + interest, totalBorrowAssets: marketState.totalBorrowAssets + interest, lastUpdate: blockTimestamp, }; return { accruedState, interestAccrued: interest }; } /** * Calculates comprehensive market APY information including supply and borrow rates. */ export async function getMarketAPYInfo( client: PublicClient, marketId: `0x${string}` ): Promise { console.log(`Fetching APY information for market: ${marketId}`); // Get current block timestamp const block = await client.getBlock({ blockTag: "latest" }); const blockTimestamp = block.timestamp; // Get market state and parameters const [marketStateResult, marketParamsResult] = await client.multicall({ contracts: [ { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "market", args: [marketId] }, { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "idToMarketParams", args: [marketId] }, ], allowFailure: false, }); const state: MarketState = { totalSupplyAssets: marketStateResult[0], totalSupplyShares: marketStateResult[1], totalBorrowAssets: marketStateResult[2], totalBorrowShares: marketStateResult[3], lastUpdate: marketStateResult[4], fee: marketStateResult[5], }; const params: MarketParams = { loanToken: marketParamsResult[0], collateralToken: marketParamsResult[1], oracle: marketParamsResult[2], irm: marketParamsResult[3], lltv: marketParamsResult[4], }; // Get token information const [loanToken, collateralToken] = await Promise.all([ getTokenInfo(client, params.loanToken), getTokenInfo(client, params.collateralToken), ]); // Initialize default values let borrowRate = 0n; let borrowAPY = 0n; let supplyAPY = 0n; let utilization = 0n; let accruedState = state; let interestAccrued = 0n; // Calculate rates if IRM is set if (params.irm !== "0x0000000000000000000000000000000000000000") { // Get borrow rate from IRM borrowRate = await client.readContract({ address: params.irm, abi: IRM_ABI, functionName: "borrowRateView", args: [params, state], }) as bigint; // Accrue interest to get current state const accrualResult = accrueInterests(state, borrowRate, blockTimestamp); accruedState = accrualResult.accruedState; interestAccrued = accrualResult.interestAccrued; // Calculate APYs borrowAPY = wTaylorCompounded(borrowRate, SECONDS_PER_YEAR); // Calculate utilization using accrued state if (accruedState.totalSupplyAssets > 0n) { utilization = wDivUp(accruedState.totalBorrowAssets, accruedState.totalSupplyAssets); } // Supply APY = Borrow APY * Utilization * (1 - Fee) supplyAPY = wMulDown( wMulDown(borrowAPY, utilization), WAD - accruedState.fee ); } return { marketId, params, state, accruedState, loanToken, collateralToken, lltvPercentage: formatLLTV(params.lltv), borrowRate, borrowAPY, supplyAPY, utilization, borrowAPYPercentage: formatWADToPercentage(borrowAPY), supplyAPYPercentage: formatWADToPercentage(supplyAPY), utilizationPercentage: formatWADToPercentage(utilization), lastUpdateTime: new Date(Number(state.lastUpdate) * 1000), interestAccrued, blockTimestamp, }; } /** * Calculates APY information for multiple markets efficiently */ export async function getMultipleMarketsAPY( client: PublicClient, marketIds: `0x${string}`[] ): Promise { console.log(`Fetching APY information for ${marketIds.length} markets...`); const apyPromises = marketIds.map(marketId => getMarketAPYInfo(client, marketId) ); return Promise.all(apyPromises); } /** * Displays comprehensive market APY information */ export function displayMarketAPY(marketInfo: MarketAPYInfo): void { console.log("\n✅ --- Morpho Market APY Information --- ✅"); console.log(`\n--- Market Overview ---`); console.log(`Market ID: ${marketInfo.marketId}`); console.log(`Loan Token: ${marketInfo.loanToken.name} (${marketInfo.loanToken.symbol})`); console.log(`Collateral Token: ${marketInfo.collateralToken.name} (${marketInfo.collateralToken.symbol})`); console.log(`LLTV: ${marketInfo.lltvPercentage.toFixed(2)}%`); console.log(`\n--- Market Liquidity (Real-time) ---`); console.log(`Total Supply: ${formatUnits(marketInfo.accruedState.totalSupplyAssets, marketInfo.loanToken.decimals)} ${marketInfo.loanToken.symbol}`); console.log(`Total Borrow: ${formatUnits(marketInfo.accruedState.totalBorrowAssets, marketInfo.loanToken.decimals)} ${marketInfo.loanToken.symbol}`); console.log(`Utilization: ${marketInfo.utilizationPercentage.toFixed(2)}%`); console.log(`\n--- Interest Rates ---`); console.log(`Borrow APY: ${marketInfo.borrowAPYPercentage.toFixed(4)}%`); console.log(`Supply APY: ${marketInfo.supplyAPYPercentage.toFixed(4)}%`); console.log(`Borrow Rate (per second): ${formatUnits(marketInfo.borrowRate, 18)}`); console.log(`\n--- Interest Accrual ---`); console.log(`Last Update: ${marketInfo.lastUpdateTime.toISOString()}`); console.log(`Interest Accrued: ${formatUnits(marketInfo.interestAccrued, marketInfo.loanToken.decimals)} ${marketInfo.loanToken.symbol}`); console.log(`Market Fee: ${formatWADToPercentage(marketInfo.state.fee).toFixed(4)}%`); console.log(`\n--- Raw Values (WAD) ---`); console.log(`Borrow APY: ${marketInfo.borrowAPY}`); console.log(`Supply APY: ${marketInfo.supplyAPY}`); console.log(`Utilization: ${marketInfo.utilization}`); console.log("\n----------------------------------------"); } /** * Displays a summary table of multiple markets */ export function displayMarketsAPYSummary(markets: MarketAPYInfo[]): void { if (markets.length === 0) { console.log("No markets to display."); return; } console.log("\n✅ --- Markets APY Summary --- ✅"); console.log("\nMarket ID | Loan/Collateral | Borrow APY | Supply APY | Utilization"); console.log("-----------------------------+---------------------+------------+------------+------------"); markets.forEach((market) => { const shortId = `${market.marketId.slice(0, 8)}...${market.marketId.slice(-8)}`; const tokenPair = `${market.loanToken.symbol}/${market.collateralToken.symbol}`; const borrowAPY = `${market.borrowAPYPercentage.toFixed(2)}%`.padStart(10); const supplyAPY = `${market.supplyAPYPercentage.toFixed(2)}%`.padStart(10); const utilization = `${market.utilizationPercentage.toFixed(2)}%`.padStart(10); console.log( `${shortId.padEnd(28)} | ${tokenPair.padEnd(19)} | ${borrowAPY} | ${supplyAPY} | ${utilization}` ); }); console.log("\n----------------------------------------"); } /** * Main function to demonstrate market APY calculations */ export async function main(): Promise { try { const client = createMainnetClient(); // Get APY information for the example market const marketAPY = await getMarketAPYInfo(client, EXAMPLE_MARKET_ID); // Display detailed results displayMarketAPY(marketAPY); // Example: Get APY for multiple markets (add more market IDs here) const multipleMarkets = [EXAMPLE_MARKET_ID]; const marketsAPY = await getMultipleMarketsAPY(client, multipleMarkets); // Display summary displayMarketsAPYSummary(marketsAPY); } catch (error) { console.error("Error in market APY calculation:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Fetching APY information for market: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 ✅ --- Morpho Market APY Information --- ✅ --- Market Overview --- Market ID: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 Loan Token: USD Coin (USDC) Collateral Token: Coinbase Wrapped BTC (cbBTC) LLTV: 86.00% --- Market Liquidity (Real-time) --- Total Supply: 522363906.351897 USDC Total Borrow: 481268778.178246 USDC Utilization: 92.13% --- Interest Rates --- Borrow APY: 7.5419% Supply APY: 6.9486% Borrow Rate (per second): 0.000000002305661164 --- Interest Accrual --- Last Update: 2025-11-07T10:56:59.000Z Interest Accrued: 0 USDC Market Fee: 0.0000% --- Raw Values (WAD) --- Borrow APY: 75418869301348230 Supply APY: 69485557173609405 Utilization: 921328545724660480 ---------------------------------------- Fetching APY information for 1 markets... Fetching APY information for market: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 ✅ --- Markets APY Summary --- ✅ Market ID | Loan/Collateral | Borrow APY | Supply APY | Utilization -----------------------------+---------------------+------------+------------+------------ 0x64d65c...883fcc64 | USDC/cbBTC | 7.54% | 6.95% | 92.13% ---------------------------------------- ``` This example shows how to calculate real-time market rates, including the crucial interest accrual step that updates market state from the last onchain update to the current block timestamp. As it is important to accrue interests on the underlying markets, Morpho Association provided a library to accrue them onchain. Feel free to refer to the [Morpho Market Snippets](https://github.com/morpho-org/morpho-blue-snippets/blob/main/src/morpho-blue/MorphoBlueSnippets.sol) for extensive example. ```solidity /// @notice Calculates the supply APY (Annual Percentage Yield) for a given market. /// @param marketParams The parameters of the market. /// @param market The market for which the supply APY is being calculated. /// @return supplyApy The calculated supply APY (scaled by WAD). function supplyAPY(MarketParams memory marketParams, Market memory market) public view returns (uint256 supplyApy) { (uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = morpho.expectedMarketBalances(marketParams); // Get the borrow rate if (marketParams.irm != address(0)) { uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets); supplyApy = borrowAPY(marketParams, market).wMulDown(1 ether - market.fee).wMulDown(utilization); } } /// @notice Calculates the borrow APY (Annual Percentage Yield) for a given market. /// @param marketParams The parameters of the market. /// @param market The state of the market. /// @return borrowApy The calculated borrow APY (scaled by WAD). function borrowAPY(MarketParams memory marketParams, Market memory market) public view returns (uint256 borrowApy) { if (marketParams.irm != address(0)) { borrowApy = IIrm(marketParams.irm).borrowRateView(marketParams, market).wTaylorCompounded(365 days); } } ```
### Interactive Interest Rate Visualization [#interactive-interest-rate-visualization] Understanding how the [AdaptiveCurveIRM](/learn/concepts/irm/#the-adaptivecurveirm) works in practice is crucial for developers integrating with Morpho Markets. This section provides a hands-on guide to implementing and visualizing the interest rate model calculations. The Morpho protocol uses a kinked interest rate model (IRM) with a target utilization of 90%.
IRM Breakdown
Below you'll find three approaches to working with this model: using the API, implementing it yourself, or understanding the mathematical foundation. **Fetch Pre-Calculated Curve Data** The easiest way to display the interest rate model is to use the Morpho API, which provides pre-calculated curve data with 101 data points (0% to 100% utilization).
*** **Step 1: Query the GraphQL Endpoint** ```graphql query GetMarketIrmCurve($marketId: String!, $chainId: Int!) { marketById(marketId: $marketId, chainId: $chainId) { marketId chain { id } irmAddress state { id utilization } currentIrmCurve { utilization supplyApy borrowApy } } } ``` API Endpoint: [api.morpho.org/graphql](https://api.morpho.org/graphql)
This endpoint returns: * Market identification data * Current utilization state * Pre-calculated interest rate curve with 101 data points
*** **Step 2: Integrate with Frontend Component** ```typescript const ChartInterestRateModelClient = ({ queryResult }) => { const marketData = queryResult?.data?.marketById; const shouldShowNoDataError = useMemo( () => marketData?.currentIrmCurve?.length === 0 || marketData?.irmAddress === undefined, [marketData?.currentIrmCurve?.length, marketData?.irmAddress] ); return ( ); }; ``` **Benefits of API Approach:** * No need to implement rate calculations * Pre-calculated data optimized for visualization * Always up-to-date with latest protocol parameters * Minimal code required for integration
**Build Your Own Rate Calculator** For developers who need to calculate rates independently or integrate calculations into backend systems, you can implement the algorithm directly.
*** **Step 1: Core Algorithm** The interest rate calculation formula: ```typescript function rateAtUtilization(apyAtTarget, utilization, fee) { let borrowApy; const minApy = Math.expm1(Math.log1p(apyAtTarget) / 4); const maxApy = Math.expm1(Math.log1p(apyAtTarget) * 4); if (utilization <= 0.9) { borrowApy = minApy + (apyAtTarget - minApy) * (utilization / 0.9); } else { borrowApy = ((maxApy - apyAtTarget) / 0.1) * (utilization - 0.9) + apyAtTarget; } const supplyApy = borrowApy * (1 - fee) * utilization; return { borrowApy, supplyApy, }; } ```
*** **Step 2: Required Parameters** To use this function, you need: 1. **`apyAtTarget`**: The target borrow APY at 90% utilization * This is derived from the `rateAtTarget` value stored in the [AdaptiveCurveIRM contract](https://docs.morpho.org/get-started/resources/contracts/irm/#mappings) * Retrieve `rateAtTarget` for your market's IRM from the contract * Convert to APY using: `apyAtTarget = (e^(rateAtTarget × secondsPerYear) - 1)` * Example: If `rateAtTarget = 1512768697` (per second rate), this converts to approximately 4.9% APY 2. **`utilization`**: Current utilization ratio (0 to 1) * Calculate as: `totalBorrow / totalSupply` * Use for calculating rates at specific points 3. **`fee`**: Protocol fee percentage (e.g., 0.1 for 10%) * Check [market state](/get-started/resources/contracts/morpho/#market-struct) for the exact fee * This affects the supply APY calculation
*** **Step 3: Generate Curve Data** ```typescript // Generate 101 data points for visualization const generateCurveData = (apyAtTarget, fee) => { const dataPoints = []; for (let i = 0; i <= 100; i++) { const utilization = i / 100; const { borrowApy, supplyApy } = rateAtUtilization( apyAtTarget, utilization, fee ); dataPoints.push({ utilization, borrowApy, supplyApy }); } return dataPoints; }; ``` **Use Cases for Direct Implementation:** * Real-time rate calculations in smart contracts * Custom analytics and modeling * Integration with proprietary systems * Independent verification of API data
**Understanding the Kinked Interest Rate Model** The AdaptiveCurveIRM uses a two-slope (kinked) model with distinct behavior before and after the target utilization.
*** **Key Parameters** 1. **Target Utilization**: 90% (the kink point) 2. **APY Range**: From minApy (\~1/4 target) to maxApy (\~4x target) 3. **Formula Elements**: * `minApy = Math.expm1(Math.log1p(apyAtTarget) / 4)` * `maxApy = Math.expm1(Math.log1p(apyAtTarget) * 4)`
*** **Two-Slope Behavior** **Below Target (0% - 90% utilization):** ``` borrowApy = minApy + (apyAtTarget - minApy) * (utilization / 0.9) ``` * Linear increase from minApy to apyAtTarget * Gentle slope to encourage borrowing * Supply rates gradually increase with utilization **Above Target (90% - 100% utilization):** ``` borrowApy = ((maxApy - apyAtTarget) / 0.1) * (utilization - 0.9) + apyAtTarget ``` * Steeper linear increase to maxApy * Discourages over-utilization * Incentivizes suppliers at high utilization
*** **Mathematical Properties at Key Points**
**At 0% utilization:** * Borrow APY = minApy (\~1/4 of target APY) * Supply APY = 0 (as utilization is 0) * Lowest rates to attract initial borrowers **At 90% utilization (target):** * Borrow APY = apyAtTarget * Supply APY = apyAtTarget × (1 - fee) × 0.9 * Optimal balance point for the market **At 100% utilization:** * Borrow APY = maxApy (\~4x target APY) * Supply APY = maxApy × (1 - fee) * Maximum rates to incentivize supply **Supply APY Formula:** ``` supplyApy = borrowApy × (1 - fee) × utilization ``` * Protocol fee is deducted from borrow interest * Supply APY scales with utilization
For more on the AdaptiveCurveIRM, explore the [technical reference](/get-started/resources/contracts/irm/).
## Asset Information [#asset-information] ### Assets [#assets] Get metadata and pricing for specific tokens.
*** ```graphql title="API" query GetAssetsWithPriceAndYield { assets(where: { symbol_in: ["wstETH", "WETH"], chainId_in: [1] }) { items { symbol address price(maxLag: 12) { usd timestamp } yield { apr lookback } chain { id network } } } } ```
This example demonstrates comprehensive asset information retrieval including: * Basic ERC20 metadata (name, symbol, decimals, total supply) * Asset validation and error handling for invalid tokens * EIP-2612 permit support detection for gasless approvals * User balance and allowance tracking * Portfolio analysis across multiple assets * Efficient batch processing using multicall * Formatted display with large number abbreviations (K, M, B, T) ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // Example token addresses for demonstration const EXAMPLE_ASSETS: Address[] = [ "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", // WBTC ]; // Comprehensive ERC20 ABI for asset information const ERC20_ABI = parseAbi([ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function totalSupply() view returns (uint256)", "function balanceOf(address account) view returns (uint256)", "function allowance(address owner, address spender) view returns (uint256)", ]); // Optional: Extended ERC20 ABI for additional metadata (some tokens support these) const EXTENDED_ERC20_ABI = parseAbi([ "function version() view returns (string)", "function DOMAIN_SEPARATOR() view returns (bytes32)", "function nonces(address owner) view returns (uint256)", ]); interface AssetInfo { address: Address; name: string; symbol: string; decimals: number; totalSupply: bigint; formattedTotalSupply: string; // Optional extended info version?: string; domainSeparator?: `0x${string}`; supportsPermit: boolean; isValid: boolean; error?: string; } interface AssetBalance { asset: AssetInfo; balance: bigint; formattedBalance: string; allowance?: bigint; formattedAllowance?: string; } interface AssetPortfolio { userAddress: Address; assets: AssetBalance[]; totalAssetsCount: number; validAssetsCount: number; invalidAssetsCount: number; } /** * Checks if an address is a valid ERC20 token by attempting to call basic functions */ async function isValidERC20( client: PublicClient, tokenAddress: Address ): Promise { try { await client.multicall({ contracts: [ { address: tokenAddress, abi: ERC20_ABI, functionName: "name" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "decimals" }, ], allowFailure: false, }); return true; } catch (error) { return false; } } /** * Checks if a token supports EIP-2612 permit functionality */ async function supportsPermit( client: PublicClient, tokenAddress: Address ): Promise { try { await client.readContract({ address: tokenAddress, abi: EXTENDED_ERC20_ABI, functionName: "DOMAIN_SEPARATOR", }); return true; } catch (error) { return false; } } /** * Formats large numbers with appropriate suffixes (K, M, B, T) */ function formatLargeNumber(value: string): string { const num = parseFloat(value); if (num >= 1e12) return `${(num / 1e12).toFixed(2)}T`; if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`; if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`; if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`; return num.toFixed(6); } /** * Retrieves comprehensive information about a single asset */ export async function getAssetInfo( client: PublicClient, tokenAddress: Address ): Promise { try { // First check if it's a valid ERC20 const isValid = await isValidERC20(client, tokenAddress); if (!isValid) { return { address: tokenAddress, name: "Invalid Token", symbol: "INVALID", decimals: 0, totalSupply: 0n, formattedTotalSupply: "0", supportsPermit: false, isValid: false, error: "Not a valid ERC20 token", }; } // Get basic ERC20 information const [name, symbol, decimals, totalSupply] = await client.multicall({ contracts: [ { address: tokenAddress, abi: ERC20_ABI, functionName: "name" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "decimals" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "totalSupply" }, ], allowFailure: false, }); // Check for permit support and get extended info const [hasPermit, version, domainSeparator] = await Promise.all([ supportsPermit(client, tokenAddress), client.readContract({ address: tokenAddress, abi: EXTENDED_ERC20_ABI, functionName: "version", }).catch(() => undefined), client.readContract({ address: tokenAddress, abi: EXTENDED_ERC20_ABI, functionName: "DOMAIN_SEPARATOR", }).catch(() => undefined), ]); const formattedTotalSupply = formatUnits(totalSupply, decimals); return { address: tokenAddress, name, symbol, decimals, totalSupply, formattedTotalSupply, version: version as string | undefined, domainSeparator: domainSeparator as `0x${string}` | undefined, supportsPermit: hasPermit, isValid: true, }; } catch (error) { return { address: tokenAddress, name: "Error", symbol: "ERROR", decimals: 0, totalSupply: 0n, formattedTotalSupply: "0", supportsPermit: false, isValid: false, error: error instanceof Error ? error.message : "Unknown error", }; } } /** * Retrieves asset information for multiple tokens efficiently */ export async function getMultipleAssetsInfo( client: PublicClient, tokenAddresses: Address[] ): Promise { console.log(`Fetching information for ${tokenAddresses.length} assets...`); const assetPromises = tokenAddresses.map(address => getAssetInfo(client, address) ); return Promise.all(assetPromises); } /** * Gets asset balances and allowances for a specific user */ export async function getAssetBalance( client: PublicClient, tokenAddress: Address, userAddress: Address, spenderAddress?: Address ): Promise { const assetInfo = await getAssetInfo(client, tokenAddress); if (!assetInfo.isValid) { return { asset: assetInfo, balance: 0n, formattedBalance: "0", }; } try { const balance = await client.readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: "balanceOf", args: [userAddress], }) as bigint; let allowance: bigint | undefined; let formattedAllowance: string | undefined; if (spenderAddress) { allowance = await client.readContract({ address: tokenAddress, abi: ERC20_ABI, functionName: "allowance", args: [userAddress, spenderAddress], }) as bigint; formattedAllowance = formatUnits(allowance, assetInfo.decimals); } return { asset: assetInfo, balance, formattedBalance: formatUnits(balance, assetInfo.decimals), allowance, formattedAllowance, }; } catch (error) { return { asset: { ...assetInfo, isValid: false, error: `Balance fetch failed: ${error instanceof Error ? error.message : "Unknown error"}`, }, balance: 0n, formattedBalance: "0", }; } } /** * Creates a complete asset portfolio for a user */ export async function getUserAssetPortfolio( client: PublicClient, userAddress: Address, tokenAddresses: Address[], spenderAddress?: Address ): Promise { console.log(`Fetching asset portfolio for user: ${userAddress}`); const balancePromises = tokenAddresses.map(tokenAddress => getAssetBalance(client, tokenAddress, userAddress, spenderAddress) ); const assets = await Promise.all(balancePromises); const validAssets = assets.filter(asset => asset.asset.isValid); const invalidAssets = assets.filter(asset => !asset.asset.isValid); return { userAddress, assets, totalAssetsCount: assets.length, validAssetsCount: validAssets.length, invalidAssetsCount: invalidAssets.length, }; } /** * Displays comprehensive asset information */ export function displayAssetInfo(assetInfo: AssetInfo): void { console.log("\n✅ --- Asset Information --- ✅"); console.log(`\n--- Basic Info ---`); console.log(`Address: ${assetInfo.address}`); console.log(`Name: ${assetInfo.name}`); console.log(`Symbol: ${assetInfo.symbol}`); console.log(`Decimals: ${assetInfo.decimals}`); console.log(`Valid ERC20: ${assetInfo.isValid ? "✅ Yes" : "❌ No"}`); if (assetInfo.error) { console.log(`Error: ${assetInfo.error}`); } if (assetInfo.isValid) { console.log(`\n--- Supply Info ---`); console.log(`Total Supply: ${assetInfo.formattedTotalSupply} ${assetInfo.symbol}`); console.log(`Total Supply (formatted): ${formatLargeNumber(assetInfo.formattedTotalSupply)} ${assetInfo.symbol}`); console.log(`Total Supply (raw): ${assetInfo.totalSupply}`); console.log(`\n--- Extended Features ---`); console.log(`Supports Permit (EIP-2612): ${assetInfo.supportsPermit ? "✅ Yes" : "❌ No"}`); if (assetInfo.version) { console.log(`Version: ${assetInfo.version}`); } if (assetInfo.domainSeparator) { console.log(`Domain Separator: ${assetInfo.domainSeparator}`); } } console.log("\n----------------------------------------"); } /** * Displays a summary table of multiple assets */ export function displayAssetsTable(assets: AssetInfo[]): void { console.log("\n✅ --- Assets Summary Table --- ✅"); const validAssets = assets.filter(asset => asset.isValid); const invalidAssets = assets.filter(asset => !asset.isValid); console.log(`\nValid Assets: ${validAssets.length} | Invalid Assets: ${invalidAssets.length}`); if (validAssets.length > 0) { console.log("\n--- Valid Assets ---"); console.log("Symbol | Name | Decimals | Total Supply | Permit"); console.log("-------------+-------------------------+----------+--------------------+-------"); validAssets.forEach(asset => { const symbol = asset.symbol.padEnd(12); const name = asset.name.length > 23 ? asset.name.slice(0, 20) + "..." : asset.name.padEnd(23); const decimals = asset.decimals.toString().padStart(8); const supply = formatLargeNumber(asset.formattedTotalSupply).padStart(18); const permit = asset.supportsPermit ? " ✅" : " ❌"; console.log(`${symbol} | ${name} | ${decimals} | ${supply} | ${permit}`); }); } if (invalidAssets.length > 0) { console.log("\n--- Invalid Assets ---"); invalidAssets.forEach(asset => { console.log(`❌ ${asset.address}: ${asset.error || "Invalid ERC20"}`); }); } console.log("\n----------------------------------------"); } /** * Displays user asset portfolio */ export function displayAssetPortfolio(portfolio: AssetPortfolio): void { console.log("\n✅ --- User Asset Portfolio --- ✅"); console.log(`\n--- Portfolio Overview ---`); console.log(`User Address: ${portfolio.userAddress}`); console.log(`Total Assets: ${portfolio.totalAssetsCount}`); console.log(`Valid Assets: ${portfolio.validAssetsCount}`); console.log(`Invalid Assets: ${portfolio.invalidAssetsCount}`); const assetsWithBalance = portfolio.assets.filter(asset => asset.asset.isValid && asset.balance > 0n ); if (assetsWithBalance.length > 0) { console.log(`\n--- Assets with Balance ---`); console.log("Symbol | Balance | Allowance (if set)"); console.log("-------------+----------------------+-------------------"); assetsWithBalance.forEach(assetBalance => { const symbol = assetBalance.asset.symbol.padEnd(12); const balance = parseFloat(assetBalance.formattedBalance).toLocaleString('en-US', { maximumFractionDigits: 6, }).padStart(20); const allowance = assetBalance.formattedAllowance ? parseFloat(assetBalance.formattedAllowance).toLocaleString('en-US', { maximumFractionDigits: 6, }) : "Not checked"; console.log(`${symbol} | ${balance} | ${allowance}`); }); } else { console.log(`\n--- No assets with balance found ---`); } console.log("\n----------------------------------------"); } /** * Main function to demonstrate asset information retrieval */ export async function main(): Promise { try { const client = createMainnetClient(); console.log("=== Asset Information Demo ===\n"); // Demo 1: Get information for a single asset console.log("1. Single Asset Info (USDC):"); const usdcInfo = await getAssetInfo(client, EXAMPLE_ASSETS[0]); displayAssetInfo(usdcInfo); // Demo 2: Get information for multiple assets console.log("\n2. Multiple Assets Info:"); const assetsInfo = await getMultipleAssetsInfo(client, EXAMPLE_ASSETS); displayAssetsTable(assetsInfo); // Demo 3: Get user portfolio (example with zero address - will show zero balances) console.log("\n3. User Portfolio (Zero Address - for demo):"); const zeroAddress = "0x0000000000000000000000000000000000000000"; const portfolio = await getUserAssetPortfolio(client, zeroAddress, EXAMPLE_ASSETS.slice(0, 3)); displayAssetPortfolio(portfolio); console.log("\n=== Demo Complete ==="); } catch (error) { console.error("Error in asset information script:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` === Asset Information Demo === 1. Single Asset Info (USDC): ✅ --- Asset Information --- ✅ --- Basic Info --- Address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 Name: USD Coin Symbol: USDC Decimals: 6 Valid ERC20: ✅ Yes --- Supply Info --- Total Supply: 41233587998.984993 USDC Total Supply (formatted): 41.23B USDC Total Supply (raw): 41233587998984993 --- Extended Features --- Supports Permit (EIP-2612): ✅ Yes Version: 2 Domain Separator: 0x06c37168a7db5138defc7866392bb87a741f9b3d104deb5094588ce041cae335 ---------------------------------------- 2. Multiple Assets Info: Fetching information for 5 assets... ✅ --- Assets Summary Table --- ✅ Valid Assets: 5 | Invalid Assets: 0 --- Valid Assets --- Symbol | Name | Decimals | Total Supply | Permit -------------+-------------------------+----------+--------------------+------- USDC | USD Coin | 6 | 41.23B | ✅ WETH | Wrapped Ether | 18 | 2.54M | ❌ USDT | Tether USD | 6 | 74.81B | ❌ DAI | Dai Stablecoin | 18 | 3.64B | ✅ WBTC | Wrapped BTC | 8 | 128.85K | ❌ ---------------------------------------- 3. User Portfolio (Zero Address - for demo): Fetching asset portfolio for user: 0x0000000000000000000000000000000000000000 ✅ --- User Asset Portfolio --- ✅ --- Portfolio Overview --- User Address: 0x0000000000000000000000000000000000000000 Total Assets: 3 Valid Assets: 3 Invalid Assets: 0 --- Assets with Balance --- Symbol | Balance | Allowance (if set) -------------+----------------------+------------------- WETH | 1,086.556748 | Not checked USDT | 201,290.112228 | Not checked ---------------------------------------- === Demo Complete === ``` This example provides complete asset analysis capabilities essential for DeFi applications, including validation, metadata extraction, and portfolio management.
## Position Tracking [#position-tracking] ### User Position on Market [#user-position-on-market] ```graphql title="API" query { marketPositions( first: 10, orderBy: BorrowShares, orderDirection: Desc where: { marketUniqueKey_in: ["0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115"] } ) { items { user { address } state { collateral borrowAssets borrowAssetsUsd } } } } ``` This example demonstrates comprehensive user position analysis including: * Real-time interest accrual for accurate borrow/supply amounts * Health factor calculation with liquidation risk assessment * Collateral valuation using oracle prices * Position safety metrics (LTV, liquidation buffer, liquidation price) * Multiple market position tracking and portfolio analysis * Conversion between shares and assets with proper rounding * Risk assessment and liquidation threshold calculations User positions require real-time interest accrual to show accurate debt amounts. The raw `position()` function returns shares, which must be converted to assets using current market state for precise calculations. ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // Morpho V1 contract address const MORPHO_BLUE_ADDRESS: Address = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // Example user and market for demonstration const EXAMPLE_USER: Address = "0x4352Cc849b33a936Ad93bB109aFDec1c89653b4f"; const EXAMPLE_MARKET_ID: `0x${string}` = "0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64"; // Morpho V1 ABI for user position operations const MORPHO_BLUE_ABI = parseAbi([ "function market(bytes32 id) view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)", "function idToMarketParams(bytes32 id) view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", "function position(bytes32 id, address user) view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)", ]); // Interest Rate Model ABI const IRM_ABI = parseAbi([ "function borrowRateView((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee) market) view returns (uint256)", ]); // Oracle ABI const ORACLE_ABI = parseAbi([ "function price() view returns (uint256)", ]); // ERC20 ABI for token information const ERC20_ABI = parseAbi([ "function symbol() view returns (string)", "function name() view returns (string)", "function decimals() view returns (uint8)", ]); type MarketParams = { loanToken: Address; collateralToken: Address; oracle: Address; irm: Address; lltv: bigint; } type MarketState = { totalSupplyAssets: bigint; totalSupplyShares: bigint; totalBorrowAssets: bigint; totalBorrowShares: bigint; lastUpdate: bigint; fee: bigint; } type UserPosition = { supplyShares: bigint; borrowShares: bigint; collateral: bigint; } type TokenInfo = { address: Address; symbol: string; name: string; decimals: number; } interface UserPositionInfo { user: Address; marketId: `0x${string}`; marketParams: MarketParams; staleMarketState: MarketState; currentMarketState: MarketState; rawPosition: UserPosition; loanToken: TokenInfo; collateralToken: TokenInfo; lltvPercentage: number; // User-specific calculated values (real-time with accrued interest) supplyAssets: bigint; borrowAssets: bigint; collateralAssets: bigint; // Formatted values formattedSupplyAssets: string; formattedBorrowAssets: string; formattedCollateralAssets: string; // Health metrics collateralPrice: bigint; maxBorrow: bigint; healthFactor: bigint; isHealthy: boolean; liquidationBuffer: bigint; currentLTV: number; maxLTV: number; // Risk metrics utilizationOfCollateral: number; liquidationPrice: bigint; // Interest accrual info interestAccrued: bigint; formattedInterestAccrued: string; lastUpdateTime: Date; blockTimestamp: bigint; } const WAD = 10n ** 18n; const VIRTUAL_ASSETS = 1n; const VIRTUAL_SHARES = 10n ** 6n; const ORACLE_PRICE_SCALE = 10n ** 36n; const MAX_UINT256 = 2n ** 256n - 1n; const wMulDown = (x: bigint, y: bigint): bigint => (x * y) / WAD; const wDivUp = (x: bigint, y: bigint): bigint => (x * WAD + y - 1n) / y; const wDivDown = (x: bigint, y: bigint): bigint => (x * WAD) / y; const mulDivDown = (x: bigint, y: bigint, z: bigint): bigint => (x * y) / z; const mulDivUp = (x: bigint, y: bigint, z: bigint): bigint => (x * y + z - 1n) / z; const wTaylorCompounded = (x: bigint, n: bigint): bigint => { const firstTerm = x * n; const secondTerm = (firstTerm * firstTerm) / (2n * WAD); const thirdTerm = (secondTerm * firstTerm) / (3n * WAD); return firstTerm + secondTerm + thirdTerm; }; /** Converts shares to assets (rounding down for supply, up for borrow) */ const toAssetsDown = (shares: bigint, totalAssets: bigint, totalShares: bigint): bigint => { if (totalShares === 0n) return shares; return mulDivDown(shares, totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); }; const toAssetsUp = (shares: bigint, totalAssets: bigint, totalShares: bigint): bigint => { if (totalShares === 0n) return shares; return mulDivUp(shares, totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); }; /** * Formats LLTV from WAD format to percentage */ function formatLLTV(lltv: bigint): number { return Number(formatUnits(lltv, 18)) * 100; } /** * Fetches token information for a given address */ async function getTokenInfo( client: PublicClient, tokenAddress: Address ): Promise { try { const [symbol, name, decimals] = await client.multicall({ contracts: [ { address: tokenAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "name" }, { address: tokenAddress, abi: ERC20_ABI, functionName: "decimals" }, ], allowFailure: false, }); return { address: tokenAddress, symbol, name, decimals, }; } catch (error) { return { address: tokenAddress, symbol: `Token(${tokenAddress.slice(0, 6)}...)`, name: `Unknown Token ${tokenAddress.slice(0, 6)}...`, decimals: 18, }; } } /** * Accrues interest on a market from its last update time to current block timestamp */ function accrueInterests( marketState: MarketState, borrowRate: bigint, blockTimestamp: bigint ): { accruedState: MarketState; interestAccrued: bigint } { const elapsed = blockTimestamp - marketState.lastUpdate; if (elapsed === 0n || marketState.totalBorrowAssets === 0n) { return { accruedState: marketState, interestAccrued: 0n }; } const interest = wMulDown( marketState.totalBorrowAssets, wTaylorCompounded(borrowRate, elapsed) ); const accruedState = { ...marketState, totalSupplyAssets: marketState.totalSupplyAssets + interest, totalBorrowAssets: marketState.totalBorrowAssets + interest, lastUpdate: blockTimestamp, }; return { accruedState, interestAccrued: interest }; } /** * Retrieves comprehensive user position information with real-time interest accrual */ export async function getUserPositionInfo( client: PublicClient, userAddress: Address, marketId: `0x${string}` ): Promise { console.log(`Fetching position for user: ${userAddress} in market: ${marketId}`); // Get current block timestamp const block = await client.getBlock({ blockTag: "latest" }); const blockTimestamp = block.timestamp; // Get market data, parameters, and user position const [marketStateResult, marketParamsResult, positionResult] = await client.multicall({ contracts: [ { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "market", args: [marketId] }, { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "idToMarketParams", args: [marketId] }, { address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "position", args: [marketId, userAddress] }, ], allowFailure: false, }); const staleMarketState: MarketState = { totalSupplyAssets: marketStateResult[0], totalSupplyShares: marketStateResult[1], totalBorrowAssets: marketStateResult[2], totalBorrowShares: marketStateResult[3], lastUpdate: marketStateResult[4], fee: marketStateResult[5], }; const marketParams: MarketParams = { loanToken: marketParamsResult[0], collateralToken: marketParamsResult[1], oracle: marketParamsResult[2], irm: marketParamsResult[3], lltv: marketParamsResult[4], }; const rawPosition: UserPosition = { supplyShares: positionResult[0], borrowShares: positionResult[1], collateral: positionResult[2], }; // Get token information const [loanToken, collateralToken] = await Promise.all([ getTokenInfo(client, marketParams.loanToken), getTokenInfo(client, marketParams.collateralToken), ]); // Initialize values let currentMarketState = staleMarketState; let interestAccrued = 0n; // Calculate current state with interest accrual if IRM is set if (marketParams.irm !== "0x0000000000000000000000000000000000000000") { // Get borrow rate from IRM const borrowRate = await client.readContract({ address: marketParams.irm, abi: IRM_ABI, functionName: "borrowRateView", args: [marketParams, staleMarketState], }) as bigint; // Accrue interest to get current state const accrualResult = accrueInterests(staleMarketState, borrowRate, blockTimestamp); currentMarketState = accrualResult.accruedState; interestAccrued = accrualResult.interestAccrued; } // Calculate user's real-time asset amounts const supplyAssets = toAssetsDown( rawPosition.supplyShares, currentMarketState.totalSupplyAssets, currentMarketState.totalSupplyShares ); const borrowAssets = toAssetsUp( rawPosition.borrowShares, currentMarketState.totalBorrowAssets, currentMarketState.totalBorrowShares ); const collateralAssets = rawPosition.collateral; // Get collateral price and calculate health metrics let collateralPrice = 0n; let maxBorrow = 0n; let healthFactor = MAX_UINT256; let isHealthy = true; let liquidationPrice = 0n; if (marketParams.oracle !== "0x0000000000000000000000000000000000000000") { collateralPrice = await client.readContract({ address: marketParams.oracle, abi: ORACLE_ABI, functionName: "price", }) as bigint; // Calculate maximum borrowing capacity maxBorrow = wMulDown( mulDivDown(collateralAssets, collateralPrice, ORACLE_PRICE_SCALE), marketParams.lltv ); // Calculate health factor if (borrowAssets > 0n) { healthFactor = wDivDown(maxBorrow, borrowAssets); isHealthy = healthFactor >= WAD; // Calculate liquidation price (price at which position becomes unhealthy) if (collateralAssets > 0n) { liquidationPrice = mulDivUp( borrowAssets * ORACLE_PRICE_SCALE, WAD, mulDivDown(collateralAssets, marketParams.lltv, WAD) ); } } } // Calculate additional metrics const liquidationBuffer = maxBorrow > borrowAssets ? maxBorrow - borrowAssets : 0n; const currentLTV = maxBorrow > 0n ? Number(wDivDown(borrowAssets, mulDivDown(collateralAssets, collateralPrice, ORACLE_PRICE_SCALE))) / 1e18 * 100 : 0; const maxLTV = formatLLTV(marketParams.lltv); const utilizationOfCollateral = maxBorrow > 0n ? Number(wDivDown(borrowAssets, maxBorrow)) / 1e18 * 100 : 0; return { user: userAddress, marketId, marketParams, staleMarketState, currentMarketState, rawPosition, loanToken, collateralToken, lltvPercentage: maxLTV, supplyAssets, borrowAssets, collateralAssets, formattedSupplyAssets: formatUnits(supplyAssets, loanToken.decimals), formattedBorrowAssets: formatUnits(borrowAssets, loanToken.decimals), formattedCollateralAssets: formatUnits(collateralAssets, collateralToken.decimals), collateralPrice, maxBorrow, healthFactor, isHealthy, liquidationBuffer, currentLTV, maxLTV, utilizationOfCollateral, liquidationPrice, interestAccrued, formattedInterestAccrued: formatUnits(interestAccrued, loanToken.decimals), lastUpdateTime: new Date(Number(staleMarketState.lastUpdate) * 1000), blockTimestamp, }; } /** * Checks if user has any position in the market */ export async function hasPosition( client: PublicClient, userAddress: Address, marketId: `0x${string}` ): Promise { const position = await client.readContract({ address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "position", args: [marketId, userAddress], }) as [bigint, bigint, bigint]; return position[0] > 0n || position[1] > 0n || position[2] > 0n; } /** * Gets user positions across multiple markets efficiently */ export async function getUserPositionsInMarkets( client: PublicClient, userAddress: Address, marketIds: `0x${string}`[] ): Promise { console.log(`Fetching positions for user ${userAddress} across ${marketIds.length} markets...`); const positionPromises = marketIds.map(marketId => getUserPositionInfo(client, userAddress, marketId) ); const allPositions = await Promise.all(positionPromises); // Filter out positions with no assets return allPositions.filter(position => position.supplyAssets > 0n || position.borrowAssets > 0n || position.collateralAssets > 0n ); } /** * Displays comprehensive user position information */ export function displayUserPosition(positionInfo: UserPositionInfo): void { console.log("\n✅ --- User Position Analysis --- ✅"); console.log(`\n--- User & Market Info ---`); console.log(`User: ${positionInfo.user}`); console.log(`Market ID: ${positionInfo.marketId}`); console.log(`Loan Token: ${positionInfo.loanToken.name} (${positionInfo.loanToken.symbol})`); console.log(`Collateral Token: ${positionInfo.collateralToken.name} (${positionInfo.collateralToken.symbol})`); console.log(`Max LTV: ${positionInfo.maxLTV.toFixed(2)}%`); console.log(`\n--- Position Assets (Real-time) ---`); console.log(`Supply: ${positionInfo.formattedSupplyAssets} ${positionInfo.loanToken.symbol}`); console.log(`Borrow: ${positionInfo.formattedBorrowAssets} ${positionInfo.loanToken.symbol}`); console.log(`Collateral: ${positionInfo.formattedCollateralAssets} ${positionInfo.collateralToken.symbol}`); console.log(`\n--- Health Metrics ---`); console.log(`Health Factor: ${formatUnits(positionInfo.healthFactor, 18)}`); console.log(`Is Healthy: ${positionInfo.isHealthy ? "✅ Yes" : "❌ No (Liquidatable!)"}`); console.log(`Current LTV: ${positionInfo.currentLTV.toFixed(2)}%`); console.log(`LTV Utilization: ${positionInfo.utilizationOfCollateral.toFixed(2)}%`); console.log(`Liquidation Buffer: ${formatUnits(positionInfo.liquidationBuffer, positionInfo.loanToken.decimals)} ${positionInfo.loanToken.symbol}`); console.log(`\n--- Price & Risk Info ---`); console.log(`Collateral Price: ${formatUnits(positionInfo.collateralPrice, 36)} ${positionInfo.loanToken.symbol} per ${positionInfo.collateralToken.symbol}`); if (positionInfo.liquidationPrice > 0n) { console.log(`Liquidation Price: ${formatUnits(positionInfo.liquidationPrice, 36)} ${positionInfo.loanToken.symbol} per ${positionInfo.collateralToken.symbol}`); // Price drop to liquidation is mathematically equivalent to: 100% - utilization of collateral // This avoids precision issues from bigint-to-number conversion const priceDropToLiquidation = 100 - positionInfo.utilizationOfCollateral; console.log(`Price Drop to Liquidation: ${priceDropToLiquidation.toFixed(2)}%`); } console.log(`\n--- Interest Accrual ---`); console.log(`Last Update: ${positionInfo.lastUpdateTime.toISOString()}`); console.log(`Interest Accrued: ${positionInfo.formattedInterestAccrued} ${positionInfo.loanToken.symbol}`); console.log(`\n--- Raw Position Data ---`); console.log(`Supply Shares: ${positionInfo.rawPosition.supplyShares}`); console.log(`Borrow Shares: ${positionInfo.rawPosition.borrowShares}`); console.log(`Collateral (raw): ${positionInfo.rawPosition.collateral}`); console.log("\n----------------------------------------"); } /** * Displays a summary table of multiple user positions */ export function displayUserPositionsTable(positions: UserPositionInfo[]): void { if (positions.length === 0) { console.log("No positions found for user."); return; } console.log("\n✅ --- User Positions Summary --- ✅"); console.log(`\nUser: ${positions[0].user}`); console.log(`Total Positions: ${positions.length}`); console.log("\nMarket | Loan/Collateral | Borrowed | Collateral | Health Factor | Status"); console.log("-----------------------------+---------------------+-------------+-------------+---------------+--------"); positions.forEach(position => { const shortMarketId = `${position.marketId.slice(0, 8)}...${position.marketId.slice(-8)}`; const tokenPair = `${position.loanToken.symbol}/${position.collateralToken.symbol}`; const borrowed = parseFloat(position.formattedBorrowAssets).toLocaleString('en-US', { maximumFractionDigits: 2 }); const collateral = parseFloat(position.formattedCollateralAssets).toLocaleString('en-US', { maximumFractionDigits: 2 }); const healthFactor = Number(formatUnits(position.healthFactor, 18)).toFixed(3); const status = position.isHealthy ? "✅ Healthy" : "❌ Risk"; console.log( `${shortMarketId.padEnd(28)} | ${tokenPair.padEnd(19)} | ${borrowed.padStart(11)} | ${collateral.padStart(11)} | ${healthFactor.padStart(13)} | ${status}` ); }); console.log("\n----------------------------------------"); } /** * Main function to demonstrate user position analysis */ export async function main(): Promise { try { const client = createMainnetClient(); console.log("=== User Position Analysis Demo ===\n"); // Check if user has a position in the market const hasPos = await hasPosition(client, EXAMPLE_USER, EXAMPLE_MARKET_ID); console.log(`User has position in market: ${hasPos ? "✅ Yes" : "❌ No"}`); if (!hasPos) { console.log("No position found. Exiting demo."); return; } // Get detailed position information const positionInfo = await getUserPositionInfo(client, EXAMPLE_USER, EXAMPLE_MARKET_ID); // Display detailed results displayUserPosition(positionInfo); // Example: Get positions across multiple markets (if you have more market IDs) const multipleMarkets = [EXAMPLE_MARKET_ID]; const userPositions = await getUserPositionsInMarkets(client, EXAMPLE_USER, multipleMarkets); // Display summary table displayUserPositionsTable(userPositions); console.log("\n=== Demo Complete ==="); } catch (error) { console.error("Error in user position analysis:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` === User Position Analysis Demo === User has position in market: ✅ Yes Fetching position for user: 0x4352Cc849b33a936Ad93bB109aFDec1c89653b4f in market: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 ✅ --- User Position Analysis --- ✅ --- User & Market Info --- User: 0x4352Cc849b33a936Ad93bB109aFDec1c89653b4f Market ID: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 Loan Token: USD Coin (USDC) Collateral Token: Coinbase Wrapped BTC (cbBTC) Max LTV: 86.00% --- Position Assets (Real-time) --- Supply: 0 USDC Borrow: 75883503.679379 USDC Collateral: 1291.00894824 cbBTC --- Health Metrics --- Health Factor: 1.472724337032200696 Is Healthy: ✅ Yes Current LTV: 58.40% LTV Utilization: 67.90% Liquidation Buffer: 35871978.968515 USDC --- Price & Risk Info --- Collateral Price: 1006.5634 USDC per cbBTC Liquidation Price: 683470337724024509145.488769364009452427844856671826760481 USDC per cbBTC Price Drop to Liquidation: 32.10% --- Interest Accrual --- Last Update: 2025-11-07T10:55:11.000Z Interest Accrued: 66.30546 USDC --- Raw Position Data --- Supply Shares: 0 Borrow Shares: 71029026727778728163 Collateral (raw): 129100894824 ---------------------------------------- Fetching positions for user 0x4352Cc849b33a936Ad93bB109aFDec1c89653b4f across 1 markets... Fetching position for user: 0x4352Cc849b33a936Ad93bB109aFDec1c89653b4f in market: 0x64d65c9a2d91c36d56fbc42d69e979335320169b3df63bf92789e2c8883fcc64 ✅ --- User Positions Summary --- ✅ User: 0x4352Cc849b33a936Ad93bB109aFDec1c89653b4f Total Positions: 1 Market | Loan/Collateral | Borrowed | Collateral | Health Factor | Status -----------------------------+---------------------+-------------+-------------+---------------+-------- 0x64d65c...883fcc64 | USDC/cbBTC | 75,883,503.68 | 1,291.01 | 1.473 | ✅ Healthy ---------------------------------------- === Demo Complete === ``` This example shows a real borrower position with high LTV utilization (99.95%) but still healthy due to the high maximum LTV (98%) of this particular market. The position demonstrates the importance of real-time interest accrual for accurate risk assessment. ### All Market Positions [#all-market-positions] ```graphql title="API" query { marketPositions( first: 10 orderBy: SupplyShares orderDirection: Desc where: { marketUniqueKey_in: [ "0x698fe98247a40c5771537b5786b2f3f9d78eb487b4ce4d75533cd0e94d88a115" ] } ) { items { market { marketId loanAsset { address symbol } collateralAsset { address symbol } } user { address } state { supplyShares supplyAssets supplyAssetsUsd borrowShares borrowAssets borrowAssetsUsd collateral collateralUsd } } } } ``` ## Risk & Oracle Data [#risk--oracle-data] ### Oracle Data [#oracle-data] ```graphql title="All Markets" query Markets($first: Int, $skip: Int, $orderBy: MarketOrderBy, $orderDirection: OrderDirection, $where: MarketFilters) { markets(first: $first, skip: $skip, orderBy: $orderBy, orderDirection: $orderDirection, where: $where) { items { marketId oracle { address type data { ... on MorphoChainlinkOracleData { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } quoteFeedOne { address } quoteFeedTwo { address } scaleFactor vaultConversionSample } ... on MorphoChainlinkOracleV2Data { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } baseVaultConversionSample quoteFeedOne { address } quoteFeedTwo { address } quoteOracleVault { address } quoteVaultConversionSample scaleFactor } } creationEvent { txHash timestamp blockNumber } } } } } ```
*** ```graphql title="Unique Market" query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { marketId oracle { address type creationEvent { txHash timestamp blockNumber } data { ... on MorphoChainlinkOracleV2Data { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } baseVaultConversionSample quoteFeedOne { address } quoteFeedTwo { address } quoteOracleVault { address } quoteVaultConversionSample scaleFactor } ... on MorphoChainlinkOracleData { baseFeedOne { address } baseFeedTwo { address } baseOracleVault { address } quoteFeedOne { address } quoteFeedTwo { address } scaleFactor vaultConversionSample } } } } } ```
### Liquidations [#liquidations] ```graphql title="API" query { marketTransactions( first: 10 orderBy: Timestamp orderDirection: Desc where: { marketUniqueKey_in: [ "0x49bb2d114be9041a787432952927f6f144f05ad3e83196a7d062f374ee11d0ee" "0x093d5b432aace8bf6c4d67494f4ac2542a499571ff7a1bcc9f8778f3200d457d" ] type_in: [Liquidation] } ) { items { blockNumber txHash logIndex type user { address } market { marketId } data { ... on MarketTransactionLiquidationData { seizedAssets repaidAssets badDebtAssets liquidator } } } } } ``` ### Market Warnings [#market-warnings] Warning `type` can be: * `not_whitelisted` & YELLOW level: The market is not listed * `bad_debt_unrealized` & RED level: Significant unrealized bad debt (>= 5% / 500 BPS of total supply, and at least $100) * `oracle_price_derivation` & RED level: The on-chain oracle price deviates from the USD-derived price by more than 10x * `oracle_unusable` & RED level: The market's oracle returns no usable price * `sustained_low_liquidity` & RED level: The market has had zero available liquidity for 5 full consecutive days * `custom`: An operator-defined warning; the level is configured per entry Warning `level` is either: * YELLOW * RED The default response returns active warnings only; market warnings have no `GREEN` level. Market warnings do not propagate to vaults: vaults carry their own warning types, and the exposure-gated ones among them are raised on the vault when its exposure to the flagged markets crosses a threshold. See [Morpho Warnings](/get-started/resources/app-ecosystem/#morpho-warnings) for the full criteria and thresholds of each type.
*** ```graphql title="API" query { markets { items { marketId warnings { type level } } } } ```
## Integration [#integration] ### Vault Listing [#vault-listing] The following query corresponds to which vault has this market in its supply queue
*** ```graphql title="API" query { marketById( marketId: "0x9103c3b4e834476c9a62ea009ba2c884ee42e94e6e314a26f04d312434191836" chainId: 8453 ) { marketId supplyingVaults { address name symbol } supplyingVaultV2s { address name symbol } } } ```
## Historical Data [#historical-data] The `historicalState` allows to get historical data for certain queries. The queries need to be backfilled to return proper data (i.e. the historical data needs to be indexed and stored). Some queries are not backfilled and are flagged as deprecated in [the Morpho API sandbox](https://api.morpho.org/graphql). Available options when using an `historicalState` query: * `startTimestamp`: beginning of the historical data in UNIX timestamp format, * `endTimestamp`: end of the historical data in UNIX timestamp format, * `interval`: interval of the historical data points (`YEAR`, `QUARTER`, `MONTH`, `WEEK`, `DAY`, `HOUR`). Inputting these options is not mandatory but it is advised to specify them to control the specific data returned. If no options are specified, the default values will be: * `startTimestamp`: 0, * `endTimestamp`: infinity, * `interval`: will adjust according to `startTimestamp` and `endTimestamp` to return around 50 data points. `historicalState` field is not accessible through the markets list queries. Historical data is only available through individual market queries like `market(id: "")` or `marketById(marketId: "", chainId: ...)`. ### Historical APYs [#historical-apys] The example below mirrors the structure shown in the Morpho API sandbox, so you can copy/paste it directly when testing queries. ```graphql title="API" query MarketById($marketId: String!, $chainId: Int!, $options: TimeseriesOptions) { marketById(marketId: $marketId, chainId: $chainId) { historicalState { borrowApy(options: $options) { x y } supplyApy(options: $options) { x y } } } } ```
*** with the following variables ```json { "marketId": "0x608929d6de2a10bacf1046ff157ae38df5b9f466fb89413211efb8f63c63833a", "chainId": 1, "options": { "startTimestamp": 1707749700, "endTimestamp": 1708354500, "interval": "HOUR" } } ```
### Historical Market States [#historical-market-states] ```graphql title="API" query MarketApys($options: TimeseriesOptions) { marketById( marketId: "0x608929d6de2a10bacf1046ff157ae38df5b9f466fb89413211efb8f63c63833a" chainId: 1 ) { marketId historicalState { supplyAssetsUsd(options: $options) { x y } borrowAssetsUsd(options: $options) { x y } } } } ``` ### Historical Asset Price [#historical-asset-price] ```graphql title="API" query { wstETHWeeklyPriceUsd: assetByAddress( address: "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0" chainId: 1 ) { historicalPriceUsd( options: { startTimestamp: 1707749700 endTimestamp: 1708354500 interval: HOUR } ) { x y } } } ``` --- # Vault V2 Public Allocator Build a Vault V2 reallocation snapshot, plan shared liquidity, and attach it to a Morpho Blue action. Source: https://docs.morpho.org/developers/borrow/tutorials/public-allocator {/* AGENT-GENERATED from public-allocator.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} This tutorial shows how the Morpho SDK finds Vault V2 liquidity and turns it into Public Allocator calls for a borrow or loan-asset withdrawal. Read the [Public Allocator overview](/learn/concepts/public-allocator/) first. Use `@morpho-org/morpho-sdk@5.7.0` or later. It re-exports every SDK primitive used below, including the direct REST path's Public Allocator fetcher. ## Choose discovery or operation planning [#choose-discovery-or-operation-planning] `VaultV2BlueReallocationData` is a complete snapshot of the markets, vaults, adapters, caps, and Public Allocator settings used by the planner. Its `computeVaultV2BlueReallocations` method has two modes: | Mode | Input | Use it for | | ------------------ | ----------------------------- | ---------------------------------------------------------------------- | | Discovery | No `operation` | Measuring friendly shared liquidity the planner can find now | | Operation planning | `operation: { type, amount }` | Building an ordered plan sized for one borrow or loan-asset withdrawal | Use operation planning for transactions. Discovery exhausts every route the planner accepts and can move more assets, incur more penalties, and add more calls than the user needs. For a specific Morpho Blue market, prefer `market.getVaultV2BlueReallocations`. It validates that the snapshot and market use the same chain before calling the planner. Each result is a flat list. One entry maps to one market-to-market or idle-to-market allocator call: ```typescript interface VaultV2BlueReallocation { readonly vault: Address; readonly from: | { readonly type: "market"; readonly adapter: Address; readonly marketParams: MarketParams; } | { readonly type: "idle" }; readonly to: { readonly adapter: Address }; readonly assets: bigint; readonly penalty: bigint; } ``` The enclosing Morpho Blue action supplies the target market parameters. Keep the returned order and pass only Vault V2 reallocations to the action. ## How the planner finds liquidity [#how-the-planner-finds-liquidity] The planner simulates the allocator and Vault V2 accounting instead of adding visible token balances. 1. It accrues every market to one timestamp and filters the loaded vaults through the optional allowlist. 2. For an operation, it simulates the borrow or withdrawal on the target market. If projected utilization stays at or below 90%, it returns no reallocations. 3. Above 90%, it computes the target top-up as `ceil(projected borrows / 90%) - projected supply`. 4. It rejects sources that fail the asset, interest-rate model, adapter, permission, penalty, or cap checks. 5. It computes an initial maximum for every eligible source. 6. It rejects target supply-share overflow, binary-searches non-shared cap upper bounds, then checks the selected amount against every cap after penalty, interest, and share rounding. 7. It keeps the largest candidate per vault, chooses the largest of those candidates, applies it to cloned state, and repeats. This produces the execution order. 8. If friendly sources cannot cover the market's absolute shortfall, it continues from that state with a 100% source-utilization ceiling. It throws when even that liquidity cannot make the operation executable. A route is eligible only when all of these checks pass: * The target uses a supported, active `AccrualVaultV2MorphoMarketV1AdapterV2` that belongs to the vault. A market source must also use a supported, active adapter that belongs to that vault. * The target loan token equals the vault asset, and the target market uses its adapter's Adaptive Curve IRM. A market source must satisfy the same asset and interest-rate-model checks. * The three Vault V2 allocation records derived from the target have non-zero absolute caps, and the target remains below the allocator's absolute ceiling. * A market source has pull permission and non-zero allocations for all three derived cap IDs. An idle source needs the vault-wide idle permission. * The source is not the target market, including when two adapters expose it. * Supplying the candidate to the target does not mint fewer shares than assets or overflow the market's `uint128` supply shares. Explicitly disabled permissions and zero caps make a route ineligible. Missing nested allocator or cap state makes the snapshot incomplete and throws a typed error instead of reporting zero liquidity. For a market source, the initial maximum is the smallest of: * the target market's `uint128` supply-asset headroom; * the Public Allocator's target headroom; * the source adapter's expected assets; * the amount withdrawable before the source reaches `maxWithdrawalUtilization`. For an idle source, the vault's current idle balance replaces the last two limits. The planner does not recycle penalty donations as new idle liquidity. The cap check needs binary search because the relationship is not linear. The penalty changes vault assets, the first call fixes the relative-cap denominator, and Morpho share conversion rounds each simulated move. A cap ID shared by the source and target can impose a lower bound or a non-monotonic rounding constraint. The planner excludes shared IDs from the upper-bound search, then validates the selected amount against every cap. An operation plan can exceed its preferred amount when a larger move is the first value that satisfies a shared-cap lower bound. If the non-shared maximum still violates a shared cap, the planner omits the candidate instead of scanning every smaller base-unit amount. Discovery metrics can therefore understate executable liquidity in an already-at-or-over-cap snapshot. One canonical market state is shared by every adapter, so each simulated move updates every later candidate that depends on that market. ## Planner options [#planner-options] | Option | Default | Effect | | -------------------------- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `enabled` | `true` | Set to `false` to return an empty plan. | | `timestamp` | Latest update in the snapshot | Sets the time for market accrual and for each vault's first simulated allocation. Pass the fetched block timestamp for a coherent plan. | | `reallocatableVaults` | Every loaded vault | Restricts discovery to the supplied address iterable. | | `maxWithdrawalUtilization` | 90% | Limits source utilization during friendly discovery. Must be between 0 and 100%. | | `maxPenalty` | 0% | Ignores vaults with a higher proportional penalty. Set this explicitly to opt into paid reallocations. | | `operation` | None | Adds an amount-aware `borrow` or `withdraw` plan. The amount must be positive. | The penalty is WAD-scaled. A value of `1_000_000_000_000_000n` accepts up to 0.1%. Each call charges `ceil(assets × penalty / WAD)` in the target loan token and adds it to the vault. ## Build the snapshot with the SDK [#build-the-snapshot-with-the-sdk] The shortest RPC-backed path is `market.getVaultV2BlueReallocationData`. It fetches the target market, each Vault V2 accrual tree, Public Allocator permissions, and allocation caps at one block. The snippets below assume that `client` is a wallet client extended with viem `publicActions` and `morphoViemExtension`, and that `market` is the target `MorphoBlue` entity. Follow [Supply collateral and borrow](/developers/borrow/tutorials/assets-flow/#integrate-with-the-morpho-sdk) for that setup. Import `publicActions` from `viem`, then add `.extend(publicActions)` before `.extend(morphoViemExtension(...))`. ### Step 1: Fetch one coherent snapshot [#step-1-fetch-one-coherent-snapshot] Use candidate Vault V2 addresses from your own allowlist or discovery service. Fetch the block once, then use it for every SDK read. ```typescript import type { Address } from "viem"; const vaultAddresses = [ process.env.VAULT_V2_ADDRESS as Address, ]; const block = await client.getBlock(); const reallocationData = await market.getVaultV2BlueReallocationData({ vaultAddresses, block: { number: block.number, timestamp: block.timestamp, }, }); ``` ### Step 2: Build an amount-aware plan [#step-2-build-an-amount-aware-plan] ```typescript import { parseUnits } from "viem"; const amount = parseUnits("500", 6); const maxPenalty = 1_000_000_000_000_000n; // 0.1% const { reallocations, data: simulatedState } = market.getVaultV2BlueReallocations({ reallocationData, options: { timestamp: block.timestamp, reallocatableVaults: vaultAddresses, maxPenalty, operation: { type: "borrow", amount }, }, }); ``` `simulatedState` contains the post-plan markets and vaults. Use it for previews and diagnostics. Pass `reallocations` to the action without sorting or regrouping it. ### Step 3: Build, authorize, simulate, and submit [#step-3-build-authorize-simulate-and-submit] ```typescript import { isRequirementSignature, type RequirementSignature, } from "@morpho-org/morpho-sdk"; const positionData = await market.getPositionData(account.address, { blockNumber: block.number, }); const borrow = market.borrow({ amount, userAddress: account.address, positionData, reallocations, }); const signatures: RequirementSignature[] = []; for (const requirement of await borrow.getRequirements()) { if (isRequirementSignature(requirement)) { signatures.push(await requirement.sign(client, account.address)); } else { const hash = await client.sendTransaction(requirement); await client.waitForTransactionReceipt({ hash }); } } const transaction = borrow.buildTx(signatures); await client.call({ account: account.address, to: transaction.to, data: transaction.data, value: transaction.value, }); const hash = await client.sendTransaction(transaction); await client.waitForTransactionReceipt({ hash }); ``` Paid Vault V2 reallocations can add a loan-token approval requirement. Their penalty does not add native-token value to the transaction. The user funds the penalty; the SDK pulls the total into Bundler3, resets the allocator allowance to zero before each non-zero penalty approval, gives the allocator an exact allowance for the call, runs every allocator call without `skipRevert`, then executes the borrow. A borrow plan also works with `supplyCollateralBorrow`. Pass it as `targetReallocations` when refinancing into the target market. For a loan-asset withdrawal, repeat Step 2 with `operation: { type: "withdraw", amount }`. Then pass that new plan to the asset-mode action: ```typescript const { reallocations: withdrawReallocations } = market.getVaultV2BlueReallocations({ reallocationData, options: { timestamp: block.timestamp, maxPenalty, operation: { type: "withdraw", amount }, }, }); const withdraw = market.withdraw({ assets: amount, userAddress: account.address, positionData, reallocations: withdrawReallocations, }); ``` ## Build the snapshot from REST data [#build-the-snapshot-from-rest-data] Use REST for the large vault and market state graph when RPC credits matter. Keep one RPC block read and the Public Allocator-specific reads because the REST API does not contain every required permission and cap. ### Use REST directly [#use-rest-directly] The flow below hydrates a best-effort `VaultV2BlueReallocationData` state graph from the Morpho REST API. It retains one RPC read for the indexed block and one deployless allocator-state read per vault. The SDK resolves the Vault V2 Blue Public Allocator address from the selected chain's registry; callers do not pass an allocator address. ### Fetch the REST graph directly [#fetch-the-rest-graph-directly] Use these `GET` endpoints under `https://api.morpho.org`: | Data | Path | | ------------------------------------ | ---------------------------------------------------------------- | | Vault configuration | `/v0/vaults-v2/{chainId}:{vault}` | | Vault accounting | `/v1/vaults-v2/{chainId}:{vault}/state` | | Vault adapters, markets, and caps | `/v0/vaults-v2/{chainId}:{vault}/allocations` | | Adapter force-deallocation penalties | `/v0/vaults-v2/{chainId}:{vault}/withdrawal-options` | | Market parameters | `/v0/blue/markets/{chainId}:{marketId}` | | Market accounting | `/v0/blue/markets/{chainId}:{marketId}/state` | | Adapter market position | `/v0/blue/markets/{chainId}:{marketId}/users/{adapter}/position` | | Oracle state | `/v0/oracles/{chainId}:{oracle}/state` | | Adaptive-curve interest-rate state | `/consumer/chains/{chainId}/markets/{marketId}/irm` | Every endpoint except the IRM endpoint wraps its payload in `{ data: ... }`. The IRM endpoint returns its payload at the root. The package request helpers and response guards are not public exports, so raw callers must provide their own validation: ```typescript import { type Address, type Hex, isAddress, isAddressEqual, isHex, size, zeroAddress, } from "viem"; type Validator = (value: unknown) => value is T; const isApiInteger = (value: unknown): value is number => typeof value === "number" && Number.isSafeInteger(value); const isDecimalString = (value: unknown): value is string => typeof value === "string" && /^(0|[1-9][0-9]*)$/.test(value); const isAddressValue = (value: unknown): value is Address => typeof value === "string" && isAddress(value); const isHexValue = (value: unknown): value is Hex => typeof value === "string" && isHex(value, { strict: true }); const isHashValue = (value: unknown) => isHexValue(value) && size(value) === 32; const requiredBigInt = (value: unknown, label: string) => { if (!isDecimalString(value)) throw new Error(`Invalid ${label}`); return BigInt(value); }; const optionalBigInt = (value: unknown, label: string) => value == null ? undefined : requiredBigInt(value, label); const requiredInteger = ( value: unknown, label: string, min = 0, max = Number.MAX_SAFE_INTEGER, ) => { if (!isApiInteger(value) || value < min || value > max) { throw new Error(`Invalid ${label}`); } return value; }; const nullableAddressOrZero = (value: unknown, label: string) => { if (value === null) return zeroAddress; if (!isAddressValue(value)) throw new Error(`Invalid ${label}`); return value; }; const nullableHexOrEmpty = (value: unknown, label: string) => { if (value === null) return "0x"; if (!isHexValue(value)) throw new Error(`Invalid ${label}`); return value; }; const nullableMetadata = (value: unknown, label: string) => { if (value === null) return undefined; if (typeof value !== "string") throw new Error(`Invalid ${label}`); return value; }; async function fetchApi( path: string, validate: Validator, responseKind: "envelope" | "root" = "envelope", ): Promise { const url = new URL(path, "https://api.morpho.org"); const response = await fetch(url, { headers: { Accept: "application/json" }, }); if (!response.ok) { throw new Error(`${response.status} ${url}`); } const body: unknown = await response.json(); const record = typeof body === "object" && body !== null && !Array.isArray(body) ? (body as Record) : undefined; const data = responseKind === "root" ? body : record?.data; if (!validate(data)) { throw new Error(`Invalid API response from ${url}`); } return data; } ``` Treat the API schema as a starting point. Some hydration fields are nullable or omitted from its required lists. Each local validator must require every field used below. It must also match `chain_id` and the requested resource identity, validate arrays and objects, and use the strict checks above. Do not replace validation with a type assertion. These conversions accept numeric zero. They reject nullish required values, empty or whitespace-only decimal strings, negative or fractional values, unsafe JSON integers, and malformed addresses or hex. Optional bigint fields preserve only `null` and `undefined` as missing. ### Track REST snapshot boundaries [#track-rest-snapshot-boundaries] When you build the state directly, align the responses that expose block metadata: 1. Validate every response and its resource identity. Convert decimal-string asset and WAD values with `requiredBigInt` or `optionalBigInt`. 2. Require the same `last_indexed_block` from vault configuration, state, and allocations; market state; oracle state; and adapter positions. Withdrawal options, market parameters, and adaptive-curve rate state do not expose this field. 3. Fetch that exact block through RPC and use its timestamp. 4. Load the target market and every market referenced by an adapter cap. 5. Load each adapter's supply shares, oracle state, and adaptive-curve rate state. 6. Set each hydrated market's `lastUpdate` to the indexed block timestamp. REST totals already include accrual through that block; using the older onchain accrual timestamp would count interest twice. 7. Require every field used to hydrate an SDK entity, including vault decimal metadata, totals, `max_rate_per_second_wad`, adapter positions, and the adaptive-curve rate. Do not replace missing state with zero. These REST calls do not all accept a block selector or return `last_indexed_block`. The indexer can advance between requests, so the assembled `VaultV2BlueReallocationData` can contain state from multiple indexed blocks. The adaptive-curve rate and force-deallocation penalty can come from another block. Market parameters also lack a block field, but they are immutable for a market ID. The API usually serves its latest indexed state, so this example accepts this edge case to save RPC reads. This example gives every market and the planner the validated block timestamp. The rate therefore has no elapsed time to affect the immediate plan, and the Public Allocator planner does not use the force-deallocation penalty. Reusing the state with a later timestamp can change accrued totals, utilization, share conversions, cap headroom, route eligibility, and reallocation amounts. Reusing its vaults for force-deallocation or in-kind redemption calculations can also use a penalty from another block. Treat the plan as a quote, simulate the final transaction, and rebuild after a failed simulation. Fetch every vault's four resources first. The guard names below stand for the local type guards described above: ```typescript const chainId = client.chain.id; const selector = (identifier: string) => `${chainId}:${encodeURIComponent(identifier)}`; const vaultRows = await Promise.all( vaultAddresses.map(async (vaultAddress) => { const id = selector(vaultAddress); const [config, state, allocations, withdrawalOptions] = await Promise.all([ fetchApi(`/v0/vaults-v2/${id}`, isVaultConfig), fetchApi(`/v1/vaults-v2/${id}/state`, isVaultState), fetchApi( `/v0/vaults-v2/${id}/allocations`, isVaultAllocations, ), fetchApi( `/v0/vaults-v2/${id}/withdrawal-options`, isVaultWithdrawalOptions, ), ]); return { config, state, allocations, withdrawalOptions }; }), ); const firstVault = vaultRows[0]; if (firstVault == null) throw new Error("No Vault V2 candidates"); const indexedBlockNumber = requiredBigInt( firstVault.config.last_indexed_block, `vault ${firstVault.config.address} indexed block`, ); const assertIndexedBlock = (value: unknown, resource: string) => { if ( requiredBigInt(value, `${resource} indexed block`) !== indexedBlockNumber ) { throw new Error(`Mixed REST indexed block for ${resource}`); } }; for (const { config, state, allocations } of vaultRows) { assertIndexedBlock( config.last_indexed_block, `vault ${config.address} config`, ); assertIndexedBlock( state.last_indexed_block, `vault ${config.address} state`, ); assertIndexedBlock( allocations.last_indexed_block, `vault ${config.address} allocations`, ); } const block = await client.getBlock({ blockNumber: indexedBlockNumber, }); ``` Collect the target market and every cap-linked market, then fetch their state and adapter positions: ```typescript import { getChainAddresses } from "@morpho-org/morpho-sdk/addresses"; import type { MarketId } from "@morpho-org/morpho-sdk/types"; const { adaptiveCurveIrm } = getChainAddresses(chainId); const marketIds = new Set([market.marketParams.id]); const adapterMarketPairs = new Map< string, { adapterAddress: Address; marketId: MarketId } >(); for (const { allocations } of vaultRows) { for (const allocation of allocations.allocations) { for (const { market_id: marketId } of allocation.caps) { if (marketId == null) continue; marketIds.add(marketId); adapterMarketPairs.set( `${allocation.adapter_address.toLowerCase()}:${marketId.toLowerCase()}`, { adapterAddress: allocation.adapter_address, marketId }, ); } } } const marketRows = await Promise.all( [...marketIds].map(async (marketId) => { const id = selector(marketId); const config = await fetchApi( `/v0/blue/markets/${id}`, isMarketConfig, ); const [state, oracleState, irmState] = await Promise.all([ fetchApi(`/v0/blue/markets/${id}/state`, isMarketState), isAddressEqual(config.oracle_address, zeroAddress) ? undefined : fetchApi( `/v0/oracles/${selector(config.oracle_address)}/state`, isOracleState, ), isAddressEqual(config.irm_address, adaptiveCurveIrm) ? fetchApi( `/consumer/chains/${chainId}/markets/${encodeURIComponent(marketId)}/irm`, isMarketIrm, "root", ) : undefined, ]); assertIndexedBlock(state.last_indexed_block, `market ${marketId}`); if (oracleState != null) { assertIndexedBlock( oracleState.last_indexed_block, `oracle ${config.oracle_address}`, ); } return { config, state, oracleState, irmState }; }), ); const marketPositions = await Promise.all( [...adapterMarketPairs.values()].map( async ({ adapterAddress, marketId }) => { const position = await fetchApi( `/v0/blue/markets/${selector(marketId)}/users/${encodeURIComponent(adapterAddress)}/position`, isMarketPosition, ); assertIndexedBlock( position.last_indexed_block, `position ${adapterAddress}:${marketId}`, ); return position; }, ), ); ``` Keep the field guards local and fail the quote when validation fails. ### Hydrate SDK entities [#hydrate-sdk-entities] Create each market from its validated REST rows. The planner does not use `price`, so preserve a missing API price as `undefined`. `rateAtTarget` is required when the market uses the chain's Adaptive Curve IRM: ```typescript import { Market, MarketParams } from "@morpho-org/morpho-sdk/entities"; type MarketRow = (typeof marketRows)[number]; const hydrateMarket = ({ config, state, oracleState, irmState, }: MarketRow) => { const rateAtTarget = isAddressEqual(config.irm_address, adaptiveCurveIrm) ? requiredBigInt( irmState?.rateAtTarget, `adaptive-curve rate ${config.market_id}`, ) : undefined; return new Market({ params: new MarketParams({ loanToken: config.loan_token, collateralToken: config.collateral_token, oracle: config.oracle_address, irm: config.irm_address, lltv: requiredBigInt( config.lltv_wad, `LLTV ${config.market_id}`, ), }), totalSupplyAssets: requiredBigInt( state.total_supply_assets, `total supply assets ${config.market_id}`, ), totalSupplyShares: requiredBigInt( state.total_supply_shares, `total supply shares ${config.market_id}`, ), totalBorrowAssets: requiredBigInt( state.total_borrow_assets, `total borrow assets ${config.market_id}`, ), totalBorrowShares: requiredBigInt( state.total_borrow_shares, `total borrow shares ${config.market_id}`, ), lastUpdate: block.timestamp, fee: requiredBigInt(state.fee_wad, `fee ${config.market_id}`), price: optionalBigInt( oracleState?.price, `oracle price ${config.market_id}`, ), rateAtTarget, }); }; const markets = marketRows.map(hydrateMarket); ``` Then hydrate each compatible adapter and its parent vault. The maps below use lowercase `adapter:market` keys and must throw when a required entry is missing: ```typescript import { AccrualVaultV2, AccrualVaultV2MorphoMarketV1AdapterV2, } from "@morpho-org/morpho-sdk/entities"; const marketById = new Map( markets.map((market) => [market.id.toLowerCase(), market]), ); const positionByAdapterMarket = new Map( marketPositions.map((position) => [ `${position.user_address.toLowerCase()}:${position.market_id.toLowerCase()}`, position, ]), ); type VaultRow = (typeof vaultRows)[number]; const hydrateVault = ({ config: vaultConfig, state: vaultState, allocations: vaultAllocations, withdrawalOptions, }: VaultRow) => { const adapters = vaultAllocations.allocations.map((allocation) => { if (allocation.adapter_kind !== "morpho_market_v1_v2") { throw new Error(`Unsupported adapter ${allocation.adapter_address}`); } const adapterMarkets = allocation.caps .flatMap(({ market_id }) => (market_id == null ? [] : [market_id])) .map((marketId) => { const market = marketById.get(marketId.toLowerCase()); if (market == null) throw new Error(`Missing market ${marketId}`); return market; }); return new AccrualVaultV2MorphoMarketV1AdapterV2( { address: allocation.adapter_address, parentVault: vaultConfig.address, skimRecipient: zeroAddress, marketIds: adapterMarkets.map(({ id }) => id), adaptiveCurveIrm, supplyShares: Object.fromEntries( adapterMarkets.map((market) => { const key = `${allocation.adapter_address.toLowerCase()}:${market.id.toLowerCase()}`; const position = positionByAdapterMarket.get(key); if (position == null) throw new Error(`Missing position ${key}`); return [ market.id, requiredBigInt( position.supply_shares, `supply shares ${key}`, ), ]; }), ), }, adapterMarkets, ); }); const assetDecimals = requiredInteger( vaultConfig.asset.decimals, `asset decimals ${vaultConfig.asset.address}`, 0, 255, ); const decimalsOffset = requiredInteger( vaultConfig.decimals_offset, `decimals offset ${vaultConfig.address}`, 0, 18, ); const liquidityAdapterAddress = nullableAddressOrZero( vaultConfig.liquidity_adapter, `liquidity adapter ${vaultConfig.address}`, ); const liquidityAdapter = isAddressEqual(liquidityAdapterAddress, zeroAddress) ? undefined : adapters.find(({ address }) => isAddressEqual(address, liquidityAdapterAddress), ); if ( !isAddressEqual(liquidityAdapterAddress, zeroAddress) && liquidityAdapter == null ) { throw new Error(`Missing liquidity adapter ${liquidityAdapterAddress}`); } const liquidityData = nullableHexOrEmpty( vaultConfig.liquidity_data, `liquidity data ${vaultConfig.address}`, ); const penaltyByAdapter = new Map( withdrawalOptions.adapter_penalties.map((row) => [ row.adapter_address.toLowerCase(), requiredBigInt( row.penalty_rate_wad, `force-deallocation penalty ${row.adapter_address}`, ), ]), ); return new AccrualVaultV2( { address: vaultConfig.address, name: nullableMetadata( vaultConfig.name, `name ${vaultConfig.address}`, ), symbol: nullableMetadata( vaultConfig.symbol, `symbol ${vaultConfig.address}`, ), decimals: assetDecimals + decimalsOffset, asset: vaultConfig.asset.address, _totalAssets: requiredBigInt( vaultState.total_assets, `total assets ${vaultConfig.address}`, ), totalSupply: requiredBigInt( vaultState.total_supply, `total supply ${vaultConfig.address}`, ), virtualShares: 10n ** BigInt(decimalsOffset), maxRate: requiredBigInt( vaultConfig.max_rate_per_second_wad, `max rate ${vaultConfig.address}`, ), lastUpdate: BigInt( requiredInteger( vaultState.last_accrual_timestamp, `last accrual timestamp ${vaultConfig.address}`, ), ), liquidityAdapter: liquidityAdapterAddress, liquidityData, liquidityAllocations: undefined, performanceFee: optionalBigInt( vaultConfig.performance_fee_wad, `performance fee ${vaultConfig.address}`, ) ?? 0n, managementFee: optionalBigInt( vaultConfig.management_fee_wad, `management fee ${vaultConfig.address}`, ) ?? 0n, performanceFeeRecipient: nullableAddressOrZero( vaultConfig.performance_fee_recipient, `performance fee recipient ${vaultConfig.address}`, ), managementFeeRecipient: nullableAddressOrZero( vaultConfig.management_fee_recipient, `management fee recipient ${vaultConfig.address}`, ), }, liquidityAdapter, adapters, requiredBigInt( vaultState.idle_assets, `idle assets ${vaultConfig.address}`, ), Object.fromEntries( adapters.map((adapter) => { const penalty = penaltyByAdapter.get(adapter.address.toLowerCase()); if (penalty == null) { throw new Error( `Missing force-deallocation penalty ${adapter.address}`, ); } return [adapter.address, penalty]; }), ), ); }; const vaults = vaultRows.map(hydrateVault); ``` The REST `total_assets` value is live at `last_indexed_block`, but `last_accrual_timestamp` can be older. Hydrating that pair can make subsequent SDK accrual count part of the gap again and affect the simulated total assets or relative-cap headroom. The relative overstatement is bounded by approximately `gapSeconds × max_rate_per_second_wad / 1e18`; multiply that fraction by `total_assets` to express it in asset units. This example accepts that drift to save RPC reads. Use the RPC snapshot path when exact cap headroom is required. The force-deallocation penalty above belongs to Vault V2 adapter accounting. It is not the caller-paid Public Allocator penalty, which the remaining onchain read returns. After producing `markets` and `vaults`, fetch the remaining allocator state and assemble the final snapshot: ```typescript import { fetchVaultV2BluePublicAllocatorData } from "@morpho-org/morpho-sdk/blue/fetch"; import { VaultV2BlueReallocationData } from "@morpho-org/morpho-sdk/entities"; const allocatorEntries = await Promise.all( vaults.map(async (vault) => ({ vault, data: await fetchVaultV2BluePublicAllocatorData(vault, client, { blockNumber: indexedBlockNumber, chainId, targetMarketParams: market.marketParams, }), })), ); const reallocationData = new VaultV2BlueReallocationData({ chainId, markets: Object.fromEntries(markets.map((item) => [item.id, item])), vaults: Object.fromEntries(vaults.map((vault) => [vault.address, vault])), allocations: Object.fromEntries( allocatorEntries.map(({ vault, data }) => [ vault.address, data.allocations, ]), ), publicAllocatorConfigs: Object.fromEntries( allocatorEntries.map(({ vault, data }) => [ vault.address, data.publicAllocatorConfig, ]), ), activeAdapters: Object.fromEntries( allocatorEntries.map(({ vault, data }) => [ vault.address, data.activeAdapters, ]), ), marketPublicAllocatorConfigs: Object.fromEntries( allocatorEntries.map(({ vault, data }) => [ vault.address, data.marketPublicAllocatorConfigs, ]), ), }); ``` The allocator fetcher uses one deployless `eth_call` per vault by default and falls back to direct reads. Leave `deployless` unset for the low-credit path. Use `deployless: "force"` only when a predictable call count matters more than fallback compatibility. Finally, run Step 2 with `timestamp: block.timestamp`. The block-tagged market accounting and allocator reads then share the API's indexed block. Untagged REST state can still come from another indexed block, and a vault's cached accrual timestamp can differ, as described above. REST hydration is not always possible. Use `getVaultV2BlueReallocationData` when the vault has another SDK-supported adapter, or when a receive-share gate is combined with a management or performance fee. Exclude the vault if the RPC path throws `UnsupportedVaultV2AdapterError`. ## Handle stale or incomplete plans [#handle-stale-or-incomplete-plans] | Failure | Response | | ------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | | `InsufficientSharedLiquidityError` | Reduce the operation or load more eligible vaults, then rebuild. | | `ReallocationWithdrawExceedsMarketSupplyError` | Reduce the loan-asset withdrawal. | | `NegativeInputError` | Keep utilization and penalty limits at or above 0%. | | `InputExceedsMaxError` | Respect the error's `field` and `max`; utilization and penalty limits cannot exceed 100%, and reallocation assets must fit in `uint128`. | | `NonPositiveInputError` | Pass a positive operation amount, and do not add zero or negative reallocation assets. | | `ChainIdMismatchError` | Rebuild the snapshot for the target market's chain. | | `UnknownReallocation*` or `ReallocationAllocationUnderflowError` | Treat the snapshot as incomplete or inconsistent and rebuild it. | | `InvalidReallocation*`, `InconsistentReallocationPenaltyError`, `MixedReallocationVersionsError`, or `ReallocationWithdrawalOnTargetMarketError` | Do not hand-edit a plan. Rebuild it and preserve its version, order, and fields. | | `BundlerErrors.UnexpectedAction` | Do not offer Vault V2 Public Allocator liquidity on that chain. | | Mixed REST indexed blocks | Discard the responses and fetch one coherent snapshot again. | | Invalid or missing REST field | Fail the quote. Do not coerce it to zero or reuse partial state. | | Simulation or submission failure | Fetch fresh state and rebuild the whole plan. Do not reuse its order or amounts. | A plan represents one block-state simulation. A cap change, another reallocation, or changing market shares can invalidate it before inclusion. Always simulate the final transaction after resolving approvals. ## Source references [#source-references] * [Vault V2 planner and state simulation](https://github.com/morpho-org/sdks/blob/%40morpho-org%2Fmorpho-sdk-v5.6.0/packages/morpho-sdk/src/entities/vaultV2BlueReallocationData.ts) * [RPC snapshot construction](https://github.com/morpho-org/sdks/blob/%40morpho-org%2Fmorpho-sdk-v5.6.0/packages/morpho-sdk/src/entities/blue/blue.ts) * [Allocator fetcher re-export](https://github.com/morpho-org/sdks/blob/%40morpho-org%2Fmorpho-sdk-v5.7.0/packages/morpho-sdk/src/blue/fetch.ts) --- # Integrate Rewards for Borrow Products Source: https://docs.morpho.org/developers/borrow/tutorials/rewards Borrowers on Morpho Markets can earn rewards that offset borrowing costs or even generate net positive yields. Integrating rewards display and claiming is essential for showing users their true cost of borrowing. ## What's Unique for Borrow Products [#whats-unique-for-borrow-products] When building borrow products, understand that borrowers earn rewards for borrowing an asset from a market **Critical integration requirement:** ``` Net Borrow APY = Borrow APY - Borrow Rewards APR ``` It is also possible to incentivize a simple collateral deposit (without the borrow action required). When rewards exceed costs, net APY becomes negative (profitable borrowing). Rewards are claimable on the side via the Merkl distribution system and do not impact user LTV. Even though the Net Borrow APY can be negative, user loan will increase and so its LTV. ## Integration Steps [#integration-steps] Follow the centralized rewards tutorials with borrow-specific considerations: ### Step 1: Fetch Market Rewards Data [#step-1-fetch-market-rewards-data] **Follow:** [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) **Borrow-specific notes:** * Query `state.rewards` on market object * Look for `borrowApr` (rewards for borrowing) * No weighted averaging needed (unlike vaults) ### Step 2: Display Rewards in Your UI [#step-2-display-rewards-in-your-ui] **Follow:** [Integrate Rewards Display](/developers/rewards/tutorials/integrate-display) **Borrow-specific notes:** * Show **net cost** prominently (borrow cost - all rewards) * Highlight when borrowing becomes profitable (negative net APY) * Break down: borrow cost, borrow rewards * List reward tokens with their respective APRs * Calculate per-position rewards based on user amounts ### Step 3: Enable Reward Claiming [#step-3-enable-reward-claiming] **Follow:** [Claim Rewards](/developers/rewards/tutorials/claim-rewards) **Important:** The claiming process is **identical** for all Morpho users (borrowers, vault depositors, market suppliers). ## Complete Integration Example [#complete-integration-example] For a full working reference implementation: **See:** [Complete Rewards Integration Guide](/developers/rewards/guides/complete-integration) This guide includes production-ready code using the [morpho-merkl-recipe](https://github.com/morpho-org/merkl-morpho-recipe). ## Quick Reference [#quick-reference] | Task | Main Tutorial | Borrow-Specific Consideration | | ------------------------- | ----------------------------------------------------------------------- | ------------------------------------- | | **Understanding rewards** | [Reward Campaigns](/developers/rewards/concepts/reward-campaigns) | Understand dual eligibility | | **Distribution system** | [Distribution System](/developers/rewards/concepts/distribution-system) | Same for all users | | **Fetch rewards** | [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) | Query single location, no aggregation | | **Display rewards** | [Integrate Display](/developers/rewards/tutorials/integrate-display) | Show net cost after rewards | | **Claim rewards** | [Claim Rewards](/developers/rewards/tutorials/claim-rewards) | Same process for all users | ## Need Help? [#need-help] * **Conceptual questions**: [Rewards for Borrowers Concept](/developers/borrow/concepts/rewards) * **Main rewards guide**: [Rewards Integration Guide](/developers/rewards/get-started) * **Technical support**: [help.morpho.org](https://help.morpho.org) --- # Fee Wrapper Source: https://docs.morpho.org/developers/earn/concepts/fee-wrapper ## What is a Fee Wrapper? [#what-is-a-fee-wrapper] A **Fee Wrapper** is a Vault V2 configured in a specific, constrained mode that enables distribution channels to add a fee layer on top of an existing Morpho Vault V2. 1. **Main Morpho Vault V2**: Handles actual capital allocation to Morpho Markets (V1, V2, future versions). This is where yield is generated. 2. **Fee Wrapper**: A lightweight **Vault V2** that deposits into the main Morpho Vault V2, with the sole purpose of adding a fee layer per customer/distributor.
Both Fee Wrapper and Morpho Vault V2 use the same Vault V2 smart contract, but with different configurations: * **Morpho Vaults V2** lock their adapter registry to the official Morpho Registry * **Fee Wrappers** lock their adapter list to a single underlying Morpho Vault V2 Fee Wrappers are not listed on app.morpho.org. ## Key Properties [#key-properties] | Property | Description | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **ERC-4626 Mostly Compliant** | Mostly compliant, with shares remaining fully ERC-20. See [Vault V2 principles](/get-started/resources/contracts/morpho-vaults-v2/#introduction-to-principles) | | **Immutably Bound** | Bound to a single Morpho Vault V2 at deployment. Can **not** be changed | | **Fee Flexibility** | Owner can configure performance fees (up to 50%) and management fees (up to 5%) | | **Simplified Configuration** | Risk management functions are abdicated, making operation simpler | | **Shared Liquidity** | Users share the liquidity of the underlying Morpho Vault V2 | ## Architecture [#architecture] A Fee Wrapper is deployed with a carefully designed split between immutable, defaulted, and configurable parameters. **Fixed at deployment** (`addAdapter` / `removeAdapter` abdicated): * The single adapter pointing to the underlying Morpho Vault V2 * The adapter registry lock (no new adapters can ever be added) **Set to recommended defaults:** * Absolute and relative caps set to maximum values * Max rate set to maximum * Force-deallocate penalty set to 2% (MAX\_FORCE\_DEALLOCATE\_PENALTY) * Liquidity adapter set to the single Morpho Vault V2 adapter * All roles (Curator, Allocator, Sentinel) default to the owner **Configurable by the owner:** * Owner address * Vault name and symbol * Performance fee (up to 50%) and management fee (up to 5%) * Fee recipient address * Gate abdication mode (`abdicateNonCriticalGates`) ## Gates & Non-Custodiality [#gates--non-custodiality] Gates control who can interact with the vault. Each gate is a contract slot that, when set, enforces on-chain rules on a specific action. A Fee Wrapper has four gates: | Gate | Action it governs | Critical? | | --------------------- | --------------------------------- | ----------------------- | | **receiveAssetsGate** | Who can deposit assets | Yes - can lock assets | | **receiveSharesGate** | Who can receive shares | Yes - can lock assets | | **sendSharesGate** | Who can send/transfer shares | Yes - can lock assets | | **sendAssetsGate** | Who can be a withdrawal recipient | No - cannot lock assets | The three **critical** gates can prevent users from accessing their own assets if misconfigured. The fourth gate (`sendAssetsGate`) only restricts who can receive withdrawn assets and cannot lock assets. By default all four gates are set to `address(0)` (permissionless): anyone can deposit, withdraw, and transfer shares freely. ### Non-custodial (recommended for DeFi) [#non-custodial-recommended-for-defi] Permanently abdicate the three critical gate setters at deployment. This locks them to `address(0)` forever - no one can ever restrict user access to assets. This action is **irreversible**. The non-critical `sendAssetsGate` remains configurable so the curator can still set withdrawal-recipient rules post-deployment if needed, without affecting asset safety. ### Compliance-ready (KYC/AML) [#compliance-ready-kycaml] Keep all gate setters configurable and set gate contracts post-deployment to enforce on-chain rules (e.g., KYC whitelists). The curator can set or change gate contracts at any time via timelocked actions. This enables compliance-ready distribution but makes the vault **partially custodial**, because the critical gates could restrict certain users from accessing their assets. Keeping the critical gate setters configurable affects non-custodial guarantees. Review the implications carefully before choosing this path. For more details on gates, see the [Gates concept page](/curate/concepts/gates/) and the [Abdicate Gates tutorial](/curate/tutorials-v2/abdicate-gates/). ## Use Cases [#use-cases] * **B2B Distribution**: Distributors can deploy one Fee Wrapper per customer, each with custom fee configurations * **Revenue Sharing**: Split native yield between the distribution channel and end users (e.g., 50/50 split via fee configuration) * **White-Label Products**: Distributor can offer Morpho yield under their own brand with their own fee structure ## Yield and Rewards [#yield-and-rewards] **Native Yield (Inside ERC-4626 Flow)** Native yield comes from interest paid by borrowers on underlying Morpho Markets. It accrues automatically to shares over time, requiring no separate claim - shares simply appreciate in value. Users withdraw the same token they deposited (e.g., USDC) plus accrued interest. Performance and management fees are applied to this yield. **External Rewards (Outside ERC-4626 Flow)** External rewards (e.g., MORPHO tokens) come from incentives provided by Morpho governance, market creators, or token issuers, and are distributed via Merkl (updated every 8 hours). These rewards require a separate claim action through Merkl and are automatically forwarded to end depositors by default. It is possible to implement custom distribution. For specific reward routing (e.g., distributor claims rewards instead of users), coordinate with the Merkl team for address remapping configuration. ## Deployment [#deployment] **Dead deposit required.** Before any user deposits into the underlying Fee Wrapper, a [dead deposit](/curate/tutorials-v2/dead-deposit/) must be executed as the very first transaction on the empty Fee Wrapper. This protects against ERC-4626 inflation attacks. If skipped, the attack may be possible. ### Curator App V2 [#curator-app-v2] Use the [Curator App V2](https://curator.morpho.org/vaults) to deploy and manage Fee Wrappers. To deploy a new Fee Wrapper, open the [Fee Wrapper creation flow](https://curator.morpho.org/fee-wrapper/create). The Curator App V2 lets you: * Deploy a new Fee Wrapper * Check whether a Fee Wrapper is properly configured * Update fees on an existing Fee Wrapper Find the factory addresses in the [addresses section](/get-started/resources/addresses/). ### Programmatic Deployment [#programmatic-deployment] For programmatic deployments, use the **FeeWrapperDeployer** periphery contract to create and configure a wrapper in a single transaction. The FeeWrapperDeployer is documented inline in its source code. Refer to the latest implementation details directly in the repository before wiring deployment automation. **[FeeWrapperDeployer.sol on GitHub](https://github.com/morpho-org/morpho-snippets/blob/main/src/vault-v2/FeeWrapperDeployer.sol)** ## Integration Guide [#integration-guide] Query the Morpho API to compute post-fee APY. No on-chain reads needed - all data comes from the API. ### 1. Query the Morpho API [#1-query-the-morpho-api] Endpoint: POST [https://api.morpho.org/graphql](https://api.morpho.org/graphql) Use the fee wrapper address directly (not the underlying Morpho Vault V2 address) along with the chainID. ```graphql query FeeWrapperAPY { feeWrapper: vaultV2ByAddress( address: "0x2861ABE7AB28484Fb8375def4A7eb56C8F53fE8F" chainId: 8453 ) { avgNetApy avgNetApyExcludingRewards performanceFee managementFee name symbol rewards { supplyApr asset { address, symbol } } adapters { items { type address ... on MorphoVaultV2Adapter { innerVault { address name avgNetApyExcludingRewards performanceFee managementFee rewards { supplyApr asset { address, symbol } } } } } } } } ``` ### 2. Read Net APY [#2-read-net-apy] The API response includes pre-computed net APY values that account for the full fee chain (inner vault fees + wrapper fees + rewards): * **`avgNetApy`** - 6h rolling average net APY, including rewards. This is the recommended value for most integrations. * **`avgNetApyExcludingRewards`** - Same as above but excluding reward incentives. These values are ready to use directly - no manual computation needed. > If rewards are redirected via Merkl address remapping, `avgNetApyExcludingRewards` is the correct value to display (end users do not receive the rewards). ### 3. Advanced: Manual APY Breakdown (Optional) [#3-advanced-manual-apy-breakdown-optional] For newly deployed fee wrappers, `avgNetApy` may be zero or noisy until sufficient history accumulates. The manual computation below uses `innerVault.avgNetApyExcludingRewards` for consistent results in those cases, and is also useful when a granular per-layer fee breakdown is needed. ``` // innerVault.avgNetApyExcludingRewards already includes inner vault fee deductions adapterYield = innerVault.avgNetApyExcludingRewards wrapperPerfFeeDeduction = max(adapterYield, 0) * feeWrapper.performanceFee nativeYield = adapterYield - wrapperPerfFeeDeduction - feeWrapper.managementFee rewardsApr = sum(innerVault.rewards[].supplyApr) netApy = nativeYield + rewardsApr ``` > **Note:** `avgNetApyExcludingRewards` is the 6h rolling average net yield after the inner vault's own performance and management fees, but excluding reward incentives. Rewards are sourced from the inner vault (`innerVault.rewards[]`) and distributed via Merkl based on the inner vault's market positions. More Fee Wrapper API data can be found [here](/developers/earn/tutorials/get-data#fee-wrapper). ## Roles & Auth Model [#roles--auth-model] All roles default to the **owner** at deployment: | Role | Default | Description | | ------------- | -------- | ----------------------------------------------------------------------------------------------------- | | **Owner** | deployer | Top-level permissions. No timelock on ownership actions. | | **Curator** | owner | Configures vault risk parameters and market allocation strategy. Most actions are timelocked. | | **Allocator** | owner | Executes liquidity allocation within Curator-approved constraints. No timelock on allocation actions. | | **Sentinel** | owner | Safety role. Can revoke pending actions and reduce risk. | **Auth tiers:** * **Owner-level**: Transfer ownership, set roles - no timelock * **Curator-level**: Change fees, fee recipients, caps - timelocked * **Allocator-level**: Allocate/deallocate to the adapter - no timelock ## Security Considerations [#security-considerations] **What the Owner CAN Do:** * Change performance fees (up to 50%, timelocked) * Change management fees (up to 5%, timelocked) * Change fee recipients (timelocked) * Add/remove allocators (timelocked) **What the Owner CANNOT Do:** * Change the underlying vault (abdicated) * Add new adapters (abdicated) * Remove the existing adapter (abdicated) * Access user assets directly * Prevent users from exiting (via forceDeallocate) --- # How can distributors generate revenue on Morpho? Source: https://docs.morpho.org/developers/earn/concepts/generate-revenue **There are different ways to generate revenue on Morpho.** Curators and distributor channels have different ways to charge fees. A distributor channel is any platform, application, or service that integrates Morpho's functionality to offer it to their own users. This includes wallets, DeFi aggregators, portfolio management apps, or custom interfaces that embed Morpho vaults. These distributors make Morpho accessible to users who might not otherwise directly interact with the Morpho protocol, and they can earn fees by facilitating these interactions. To generate revenue, a distributor has several possibilities developed below: * Own its own revenue layer (a Fee Wrapper or a Vault V2 Wrapper) * Have an agreement with a curator (onchain or offchain) ## Fees [#fees] Morpho Vault V2 can charge two types of fees: a **performance fee** on native yield and a **management fee** on assets under curation. See [Fee](/curate/concepts/fee/) for detailed fee mechanics and caps. ## Architecture [#architecture] Here is the setup of the current Earn architecture with USDC as an example
Here is the vault model illustrating asset flow in terms of allocation
## Examples of possible configurations [#examples-of-possible-configurations] | Model | Control | Trust Required | Complexity | Best For | | ---------------------- | ------- | -------------- | ---------- | ------------------------------------- | | **Fee Wrapper** | High | None | Low | B2B distribution, per-customer vaults | | **V2 Wrapper** | High | None | Low | Independent distributors | | **Offchain Agreement** | Shared | High | Medium | Strategic partnerships | | **Onchain Splitter** | Shared | None | High | Formal trustless partnerships | *** ### 1. Fee Wrapper [#1-fee-wrapper]
A **Fee Wrapper** is a Vault V2 configured in a specific, constrained mode that enables distribution channels to add a fee layer on top of an existing Morpho Vault V2. Fee Wrappers are the recommended approach for B2B distribution, per-customer vaults, and white-label products. They provide high control with no trust required and low complexity. For full details on architecture, gates & non-custodiality, deployment, and integration, see the dedicated [Fee Wrapper](/developers/earn/concepts/fee-wrapper) page. **The fee recipient wallet must be able to interact with the vault**. Fees are always minted as shares. To convert them to the underlying asset, the fee recipient must call `redeem()` at some point. Two options: * **Direct redemption**: the fee recipient periodically calls `redeem()` themselves. * **Delegated redemption**: the fee recipient calls `approve(claimerAddress, type(uint256).max)` once (approval on the vault receipt token, not the underlying asset), then another address can call `redeem(shares, feeRecipient, feeRecipient)` on an ongoing basis. The fee recipient must be an address capable of interacting with the vault (EOA, smart wallet, multisig, etc.). A fully passive address that never interacts with the chain won't be able to actually receive the fee in the underlying asset. *** ### 2. Offchain Agreement Model [#2-offchain-agreement-model] **Setup:** Distributor and curator establish a legal revenue-sharing agreement for liquidity provided. **Two Options:** **Option A - New Dedicated Vault:**
* Curator launches a new dedicated vault exclusively for the distributor's users * Fee split negotiated up-front (e.g., 50/50 on performance fees) * Clean attribution of liquidity and fees *** **Option B - Existing Vault:**
* Distributor directs users to existing vault * Revenue share calculated based on tracked liquidity contribution * Requires tracking mechanism (transaction mapping, user addresses, time-weighted deposits) **Considerations:** * Legal contract enforces agreement * Option B requires reliable attribution system * Settlement handled offchain (periodic payouts) **Use Case:** Best for strategic partnerships where trust exists or when onchain complexity isn't warranted. *** ### 3. Onchain Splitter Contract Model [#3-onchain-splitter-contract-model]
**Setup:** Smart contract automatically splits fees between curator and distributor based on pre-defined terms. **Requirements:** * Dedicated new vault for the partnership * Splitter contract receives fee recipient role * Automated distribution of fees to both parties **Fee Flow:** 1. Vault accrues performance/management fees 2. Fees sent to splitter contract 3. Contract automatically distributes per agreement (e.g., 60% curator / 40% distributor, or 50/50) **Advantages:** * Trustless execution * Transparent onchain tracking * No manual settlement required **Trade-offs:** * Requires dedicated vault (curator unlikely to share existing vault) * Additional smart contract complexity * Gas costs for splitting operations **Use Case:** Best for formal partnerships requiring trustless, transparent revenue sharing with onchain guarantees. --- # Rewards for Lenders Source: https://docs.morpho.org/developers/earn/concepts/rewards Vault depositors on Morpho can earn additional rewards beyond base lending yield through incentive campaigns. These rewards significantly enhance total returns and are a key feature of competitive earn products. ## Overview [#overview] When you deposit into a Morpho Vault, you can earn rewards from **two distinct sources**: 1. **Direct Vault Campaigns**: Rewards distributed directly to vault depositors. Morpho Vault V2 can also surface rewards forwarded from its underlying allocations when campaigns are configured to flow through to end users (see [reward forwarding](/developers/rewards/concepts/reward-campaigns#reward-forwarding)). 2. **Forwarded Market Campaigns**: Rewards earned from underlying markets where the vault allocates liquidity The total yield a depositor may earn combines the native lending APY with reward APR from both sources: ``` Net APY = Native Vault APY + Vault Campaign APR + Forwarded Market Campaign APR - Performance Fee - Management Fee ``` ## What Makes Vault Rewards Unique [#what-makes-vault-rewards-unique] ### Reward Forwarding [#reward-forwarding] This is the key differentiator for vault depositors. When a vault allocates liquidity to underlying variable rate markets that have active supply campaigns, those market-level rewards are **automatically forwarded** to vault depositors. **How it works:** 1. A market has an active supply campaign 2. Your vault allocates liquidity to that market as a supplier 3. The vault contract earns those market rewards 4. Rewards are attributed to individual vault depositors by Merkl's offchain computation 5. You claim these forwarded rewards directly **Why this matters:** * You benefit from active campaigns across the markets your vault uses * Vault curators optimize allocations, and you receive rewards automatically * More capital-efficient than directly supplying to individual markets ### Complete APY Components [#complete-apy-components] The net APY shown to users combines multiple components: ``` Net APY = Native Vault APY + Rewards APR - Performance Fee - Management Fee ``` Where: * **Native Vault APY**: Native vault yield before fees and rewards * **Rewards APR**: Vault rewards + weighted market rewards * **Performance Fee**: Applied only to base APY * **Management Fee**: Applied to total assets deposited in the vault ## Distribution System [#distribution-system] Rewards distribution has migrated from the Morpho URD system to Merkl. All campaigns from July 2025 onwards use Merkl exclusively. For unclaimed historical rewards, visit [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). The claiming process is **identical** for all Morpho users, regardless of whether they're depositors or borrowers. **For complete details on distribution systems, see:** * [Reward Campaigns](/developers/rewards/concepts/reward-campaigns) * [Distribution System](/developers/rewards/concepts/distribution-system) ## Learn More [#learn-more] For comprehensive information about integrating rewards: * **[Rewards Integration Guide](/developers/rewards/get-started)**: Complete overview and integration paths * **[Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data)**: Complete API queries and calculations * **[Claim Rewards](/developers/rewards/tutorials/claim-rewards)**: Claim implementation via Merkl * **[Integrate Display](/developers/rewards/tutorials/integrate-display)**: UI/UX best practices ## Implementation [#implementation] For practical implementation guidance specific to earn products, see: * [Integrate Rewards for Earn Products](/developers/earn/tutorials/rewards) --- # Vaults & ERC4626 Mechanics Source: https://docs.morpho.org/developers/earn/concepts/vault-mechanics **Morpho Vault V2** exposes the standard [ERC‑4626](https://eips.ethereum.org/EIPS/eip-4626) interface for tokenized vaults. Understanding these mechanics is essential for building robust Earn integrations. ## ERC4626 Standard [#erc4626-standard] The ERC‑4626 standard defines four core functions for deposits and withdrawals. ### Core Functions [#core-functions] #### Deposit [#deposit] ```solidity // Deposit a specific amount of underlying assets. // Example: deposit 10 USDC and receive N vault shares. function deposit(uint256 assets, address receiver) external returns (uint256 shares); // Mint a specific amount of shares. // Example: mint N shares; the corresponding assets are pulled from the user. function mint(uint256 shares, address receiver) external returns (uint256 assets); ``` #### Withdraw [#withdraw] ```solidity // Withdraw a specific amount of assets. // Example: withdraw 10 USDC; N shares are burned. function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); // Redeem a specific amount of shares for assets. function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); ``` ## Assets vs. Shares [#assets-vs-shares] **Assets** are the underlying token (e.g., USDC, WETH).\ **Shares** represent proportional ownership of the vault and typically appreciate as yield accrues. ### Conversions [#conversions] ```ts // Convert assets to shares (pre‑deposit expectation). const shares = await vault.previewDeposit(assetAmount); // Convert shares to assets at the current rate. const assets = await vault.convertToAssets(shareAmount); // Estimate shares needed to withdraw a target asset amount. const sharesNeeded = await vault.previewWithdraw(assetAmount); // Estimate assets received when redeeming shares. const assetsReceived = await vault.previewRedeem(shareAmount); ``` ## Share Price Appreciation [#share-price-appreciation] Vault performance is reflected in the share price: ```ts // 1 vault share = 1e18 const sharePrice = await vault.convertToAssets(10n ** 18n); // Example: totalAssets grows from 1.0M to 1.1M with 1.0M shares // -> price from $1.00/share to $1.10/share (≈10%) ``` ## Inflation Attack Protection [#inflation-attack-protection] ERC4626 vaults are susceptible to [share inflation attacks](https://docs.openzeppelin.com/contracts/5.x/erc4626), where an attacker manipulates the share price to cause rounding losses for subsequent depositors. This affects all ERC4626-compliant implementations. ### The Protection Mechanism [#the-protection-mechanism] Before interacting with a vault, verify that a **dead deposit** has been made to address `0x000000000000000000000000000000000000dEaD`. This initial deposit establishes a baseline share supply that makes inflation attacks economically unfeasible. **The Check:** ```ts // Verify the vault has adequate dead deposit protection const deadAddress = "0x000000000000000000000000000000000000dEaD"; const deadShares = await vault.balanceOf(deadAddress); // Minimum threshold: 1e9 shares (~$10 equivalent for most assets) if (deadShares < 1_000_000_000n) { throw new Error("Vault lacks adequate inflation protection"); } ``` **Why 1e9 shares?** This threshold ensures that the cost to manipulate the share price exceeds any potential gain from the attack. The specific value (1e9 shares) is a conservative standard that works across different asset decimals and price points. For assets with less than 9 decimals, the recommended minimum is 1e12 shares to prevent rounding issues (non-critical). **Implementation Notes:** * The dead deposit does not need to be made atomically with your interaction * Once established, it cannot be withdrawn (by design) * Morpho Vault V2 integrations should verify that this protection is in place * Curators and vault deployers are responsible for ensuring this protection exists This protection is essential for vault security. Always verify the dead deposit exists before integrating a vault into production systems. Learn more about the attack mechanism in the [OpenZeppelin ERC4626 documentation](https://docs.openzeppelin.com/contracts/5.x/erc4626). ## Additional Considerations: Slippage [#additional-considerations-slippage] ERC4626 functions do not include built-in slippage checks. While not a security requirement (when dead deposit protection is in place), slippage checks can improve user experience by preventing unexpected exchange rate movements between transaction simulation and execution. **When to consider slippage protection:** * Share price may shift due to interest accrual or other vault activity between simulation and inclusion * User-facing applications benefit from showing expected vs. actual amounts received * Implement min-received checks around ERC4626 operations for direct integrations See the [Assets Flow tutorial](/developers/earn/tutorials/assets-flow#slippage-for-user-experience) for integration examples. --- # Wallet Structures Source: https://docs.morpho.org/developers/earn/concepts/wallet-structures When integrating Morpho Earn, the wallet architecture you choose determines how user assets flow onchain, who holds custody of assets, and how accounting is handled. This page presents the five main wallet structures available to integrators. ## Overview [#overview] | # | Structure | Custody | Onchain positions | | - | ------------------------------- | ---------- | -------------------- | | 1 | Omnibus only | Integrator | Single shared wallet | | 2 | Omnibus + custodial wallets | Integrator | Per-user wallets | | 3 | Custodial wallets only | Integrator | Per-user wallets | | 4 | Omnibus + non-custodial wallets | User | Per-user wallets | | 5 | Non-custodial wallets only | User | Per-user wallets | ## 1. Omnibus Only [#1-omnibus-only] All user assets are pooled in one omnibus account, with virtual accounting handled off-chain. * **Typical Actors**: CEXs & CeFi * **Tech Providers**: Bridge, Fireblocks, Taurus or custom KMS * **Earn integration key points**: * Offchain accounting needed (already standard for CeFi) * Integrator must handle reward distribution to user balances
## 2. Omnibus + Custodial Wallets [#2-omnibus--custodial-wallets] Default omnibus entry point, but with per-user onchain wallets (custodial: the user does not hold the private keys). * **Typical Actors**: CEX, CeFi, neo-banks * **Tech Providers**: Bridge, Fireblocks, Taurus * **Earn integration key points**: * Individualized positions onchain * Integrator side must manage reward allocation & gas efficiency
## 3. Custodial Wallets Only [#3-custodial-wallets-only] Only per-user wallets, fully managed by the integrator (no omnibus). * **Typical Actors**: Stripe-like models, custodial fintechs * **Tech Providers**: Bridge, Fireblocks, Taurus * **Earn integration key points**: Straightforward Earn setup, no omnibus complexity
## 4. Omnibus + Non-Custodial Wallets [#4-omnibus--non-custodial-wallets] Omnibus for entry, but users have non-custodial wallets with some integrator-controlled features. * **Typical Actors**: CEXs onchain oriented * **Tech Providers**: Smart contract wallets (Alchemy, Gelato), SDKs & AA tooling (Privy, Turnkey) * **Earn integration key points**: * Earn flows are user-controlled onchain or in the app * Integrator can retain some control logic
## 5. Non-Custodial Wallets Only [#5-non-custodial-wallets-only] Pure onchain setup. No omnibus, users fully manage their own wallets. * **Typical Actors**: Onchain protocols * **Tech Providers**: Non-custodial wallets (Metamask, Rabby, Safe, Ledger, ...) * **Earn integration key points**: n/a (fully decentralized)
--- # Yield & Fees Source: https://docs.morpho.org/developers/earn/concepts/yield-fees Understanding how yield is generated and fees are applied in Morpho Vaults is crucial for building accurate earn products and setting proper user expectations. ## Yield Generation [#yield-generation] **Morpho Vaults V2** generate yield through the adapters enabled by the curator and allocator. Depending on strategy, those adapters can route capital into Morpho markets and other approved yield sources while reporting the vault's real-time asset value back to the parent vault.
### Where does the yield comes from? [#where-does-the-yield-comes-from] ### Borrower Interest [#borrower-interest] Borrowers in underlying Morpho Markets pay interest ### Market Distribution [#market-distribution] Interest flows to lenders in each market ### Vault Collection [#vault-collection] Vault collects interest as a lender across multiple markets ### Share Price Increase [#share-price-increase] Vault's total assets increase, raising share price ### Depositor Interest [#depositor-interest] Depositors earn the interest paid by borrowers as the share of their position increased in token's value amount. ## Fee Mechanism [#fee-mechanism] **Morpho Vaults V2** implement a performance fee and a management fee that align curator incentives with vault performance. ### Performance Fee [#performance-fee] The fee is taken as a cut on the native yield earned by allocating capital, and is capped at 50%. ### Management Fee [#management-fee] For Morpho Vaults V2 only, this management fee is taken on the total asset deposited on the vault, and is capped at 5%. ## Integration Best Practices [#integration-best-practices] ### Real-Time Updates [#real-time-updates] * Update share prices regularly * Show pending yield that hasn't been reflected in share price yet by accruing interests on the underlying markets * Display historical performance charts ### User Education [#user-education] * Clearly explain that performance fees only apply to yield, not principal, while management fee apply on the principal. ### Performance Monitoring [#performance-monitoring] * Track vault performance against benchmarks * Alert users to significant strategy changes * Provide detailed yield attribution --- # Earn: Developer Resources Source: https://docs.morpho.org/developers/earn/resources/all This page is your central hub for all the tools, links, and data sources you need to build on Morpho's Earn product (Morpho Vaults). ## Onchain Data & Contracts [#onchain-data--contracts] These resources are for interacting directly with the blockchain. * **Vault Addresses**: While you can find the factory (the main address used to deploy all vaults) list of all core contracts on the main [Addresses page](/get-started/resources/addresses/), the best way to dynamically fetch all deployed Morpho vaults is via the [Morpho API](/developers/earn/tutorials/get-data). * **Contract Specifications**: For detailed function signatures, events, and errors, refer to the canonical [Morpho Vault V2 Contract Specs](/get-started/resources/contracts/morpho-vaults-v2/). * **GitHub Repositories**: * [`vault-v2`](https://github.com/morpho-org/vault-v2): The canonical Morpho Vault V2 implementation. ## Offchain Data & Tools [#offchain-data--tools] These tools provide easier access to onchain data for frontends, backends, and analytics. * **Morpho API (Recommended)**: The simplest way to fetch comprehensive data about vaults, their states, APYs, and user positions. * **API Playground**: [api.morpho.org/graphql](https://api.morpho.org/graphql) * **Tutorial**: [Using the API for Vaults](/developers/earn/tutorials/get-data) * **SDKs**: A suite of TypeScript libraries to accelerate your development. * **`@morpho-org/morpho-sdk`**: The default abstraction layer - ready-to-send transactions, plus core entity classes for Vaults and Markets (`@morpho-org/morpho-sdk/entities`) and Viem-based fetchers (`@morpho-org/morpho-sdk/fetch`). * **`@morpho-org/simulation-sdk`**: Simulates user interactions to predict outcomes before sending a transaction. * **Learn more**: [Introduction to Morpho SDKs](/developers/sdks/get-started/) ## Code & Examples [#code--examples] * **Solidity Snippets**: The [`morpho-blue-snippets`](https://github.com/morpho-org/morpho-blue-snippets) repository contains simple, educational Solidity contracts for common vault interactions. --- # App Guide Source: https://docs.morpho.org/developers/earn/tutorials/app-guide --- # Depositing & Withdrawing from Vaults Source: https://docs.morpho.org/developers/earn/tutorials/assets-flow {/* AGENT-GENERATED from assets-flow.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} **Morpho Vault V2** follows the standard [ERC‑4626](https://eips.ethereum.org/EIPS/eip-4626) interface for deposit and withdrawal operations. That means `deposit()`, `withdraw()`, `mint()`, and `redeem()` behave like familiar tokenized vault flows for new Earn integrations on Morpho. **Morpho Vault V2** has a non-conventional behavior on max functions (`maxDeposit`, `maxMint`, `maxWithdraw`, `maxRedeem`): they always return zero. See the [Morpho Vaults V2 reference](/get-started/resources/contracts/morpho-vaults-v2#maxdeposit) for details. For background on ERC4626 mechanics and vault architecture, see [Vaults & ERC4626 Mechanics](/developers/earn/concepts/vault-mechanics). This guide shows the recommended integration path for Morpho Vault deposits and withdrawals: the [**Morpho SDK**](/developers/sdks/morpho-sdk/) ([`@morpho-org/morpho-sdk`](https://github.com/morpho-org/sdks/tree/main/packages/morpho-sdk)). It builds final, ready-to-send `viem` transactions and resolves all on-chain pre-requisites for you - ERC-20 approvals, Permit / Permit2 signatures, native-token wrapping, slippage protection, and Bundler3 routing - whether you're building a dApp frontend or a backend service. ## Key Concepts: Assets vs. Shares [#key-concepts-assets-vs-shares] When interacting with ERC4626 vaults, you have two approaches for deposits and withdrawals: an **asset-first** approach or a **shares-first** approach. Understanding the difference is key to a robust integration. | Approach | Deposit Function | Withdrawal Function | Description | | ---------------- | ---------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------- | | **Asset-First** | `deposit(assets, ...)` | `withdraw(assets, ...)` | You specify the exact amount of the underlying token (e.g., USDC, WETH) you want to deposit or withdraw. | | **Shares-First** | `mint(shares, ...)` | `redeem(shares, ...)` | You specify the exact number of vault shares you want to mint or redeem. | **Best Practice:** * For **deposits**, `deposit()` is the most common and intuitive function. * For **full withdrawals**, `redeem()` is recommended. Redeeming all of a user's shares ensures their balance goes to zero and avoids leaving behind small, unusable amounts of "dust." * For **partial withdrawals** where a user needs a specific amount of the underlying asset, `withdraw()` is appropriate. ## Prerequisites [#prerequisites] Before you begin, you will need: * The address of the Morpho Vault you want to interact with. You can find active vaults using the [Morpho API](/developers/earn/tutorials/get-data). * An account with a balance of the vault's underlying asset (e.g., WETH for a WETH vault). ## Vault Safety: Inflation Attack Protection [#vault-safety-inflation-attack-protection] Before integrating with any ERC4626 vault, verify that adequate inflation attack protection is in place. All ERC4626-compliant vaults, including Morpho Vaults, require a **dead deposit** to protect against share inflation attacks. This protection must be verified before your first interaction with a vault. **Verification Check:** ```ts // Check that the vault has adequate protection const deadAddress = "0x000000000000000000000000000000000000dEaD"; const deadShares = await vault.balanceOf(deadAddress); if (deadShares < 1_000_000_000n) { throw new Error("Vault lacks required inflation protection"); } ``` **Key Points:** * The dead deposit must be at least **1e9 shares** for assets with more than 9 decimals, or **1e12 shares** otherwise (approximately $1 equivalent). * This check should be performed during vault integration/whitelisting, not on every transaction * Properly protected vaults make inflation attacks economically unfeasible * Morpho Vault V2 integrations should ensure this protection is established by curators For a detailed explanation of how this protection works, see [Vault Mechanics: Inflation Attack Protection](/developers/earn/concepts/vault-mechanics#inflation-attack-protection). ## Integrate with the Morpho SDK [#integrate-with-the-morpho-sdk] For an end-to-end walkthrough of every vault action this SDK exposes (deposit, withdraw, redeem, force-withdraw) see the [**Vault subpage**](/developers/sdks/morpho-sdk/vault/); the [Morpho SDK page](/developers/sdks/morpho-sdk/) covers the `getRequirements` flow, the builder = signer invariant, and error classes. ### Step 1: Install and set up the client [#step-1-install-and-set-up-the-client] ```bash npm install @morpho-org/morpho-sdk@5.8.0 viem@^2.0.0 # or pnpm add @morpho-org/morpho-sdk@5.8.0 viem@^2.0.0 # or yarn add @morpho-org/morpho-sdk@5.8.0 viem@^2.0.0 ``` ```typescript import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { mainnet } from "viem/chains"; import { morphoViemExtension } from "@morpho-org/morpho-sdk"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const client = createWalletClient({ account, chain: mainnet, transport: http(process.env.RPC_URL_MAINNET), }).extend( morphoViemExtension({ // Enables Permit / Permit2 in getRequirements() so users skip the extra approve tx. supportSignature: true, }), ); const vault = client.morpho.vaultV2("0xVaultAddress...", mainnet.id); ``` ### Step 2: Deposit [#step-2-deposit] ```typescript import { parseUnits } from "viem"; const vaultData = await vault.getData(); // fresh on-chain state with accrued interest const deposit = vault.deposit({ amount: parseUnits("1.0", 18), // 1 underlying token userAddress: account.address, vaultData, }); // 1. Resolve approvals / permits before sending the deposit const requirements = await deposit.getRequirements(); const signatures = []; for (const req of requirements) { if ("sign" in req) { // Permit / Permit2: capture the off-chain signature signatures.push(await req.sign(client, account.address)); } else { // ERC-20 approval: send the approval transaction await client.sendTransaction(req); } } // 2. Build and send the deposit tx (same client that built it - "builder = signer"). // buildTx takes the collected requirement signatures as an array. const depositTx = deposit.buildTx(signatures); const depositTxHash = await client.sendTransaction(depositTx); console.log("Deposit successful:", depositTxHash); ``` ### Step 3: Withdraw / redeem [#step-3-withdraw--redeem] ```typescript // Redeem all of a user's shares (recommended for full exits - leaves no dust) const userShares = vaultData.toShares(parseUnits("1.0", 18)); const redeem = vault.redeem({ shares: userShares, userAddress: account.address, }); const redeemTxHash = await client.sendTransaction(redeem.buildTx()); console.log("Withdrawal successful:", redeemTxHash); ``` For partial withdrawals by exact asset amount, use `vault.withdraw({ amount, userAddress })` instead of `redeem`. If borrowed liquidity prevents a normal exit, follow [Exit an illiquid vault in kind](/developers/earn/tutorials/in-kind-redemption/) to preview and transfer the vault's positions. ## Additional Considerations [#additional-considerations] ### Slippage for User Experience [#slippage-for-user-experience] Vault deposits convert assets into shares. Between quote and execution, the vault share price can move because interest accrues or vault state changes. Slippage tolerance lets the transaction revert if the user would receive materially fewer shares than expected. The SDK computes the `maxSharePrice` guard from fresh vault data and your `slippageTolerance`, then routes the deposit through Bundler3 / GeneralAdapter1. The SDK default is `0.03%`; the maximum accepted value is `10%`. A tighter value gives stronger price protection but can cause more reverts when vault state changes before inclusion. ### Step 1: Set the deposit inputs [#step-1-set-the-deposit-inputs] Choose the asset amount and maximum tolerated movement before building the SDK transaction. Slippage is expressed in WAD units: `parseUnits("0.01", 18)` means 1%. ```typescript import { parseUnits, type Address } from "viem"; import { mainnet } from "viem/chains"; import { isRequirementSignature, type RequirementSignature, } from "@morpho-org/morpho-sdk"; // Assumes `client` is a viem wallet client extended with `morphoViemExtension()` // and `account` is the connected signer. const vaultAddress = "0xVaultAddress..." as Address; const userAddress = account.address; const amount = parseUnits("1.0", 18); // replace 18 with the underlying asset decimals const slippageTolerance = parseUnits("0.01", 18); // 1% in WAD units const vault = client.morpho.vaultV2(vaultAddress, mainnet.id); const vaultData = await vault.getData(); // fresh on-chain state with accrued interest ``` ### Step 2: Build the protected deposit transaction [#step-2-build-the-protected-deposit-transaction] Pass `vaultData` and `slippageTolerance` to the deposit builder so the SDK can build the onchain share-price guard. ```typescript const deposit = vault.deposit({ amount, userAddress, vaultData, slippageTolerance, }); ``` ### Step 3: Resolve approvals and permits [#step-3-resolve-approvals-and-permits] `getRequirements()` returns approval transactions plus an optional Permit / Permit2 signature. Resolve them before sending the final deposit. ```typescript const requirements = await deposit.getRequirements(); const requirementSignatures: RequirementSignature[] = []; for (const requirement of requirements) { if (isRequirementSignature(requirement)) { requirementSignatures.push(await requirement.sign(client, userAddress)); } else { await client.sendTransaction(requirement); } } ``` ### Step 4: Send the protected deposit [#step-4-send-the-protected-deposit] Build the final transaction with the optional signature, then send it with the same client that built the transaction. ```typescript const depositTxHash = await client.sendTransaction(deposit.buildTx(requirementSignatures)); console.log("Deposit sent:", depositTxHash); ``` See also [Vault Mechanics: Slippage Considerations](/developers/earn/concepts/vault-mechanics#additional-considerations-slippage) for more context. --- # Get Data Source: https://docs.morpho.org/developers/earn/tutorials/get-data ## Before Starting [#before-starting] In this tutorial, you might see different ways to fetch data for Morpho Vaults: * **API**: Using the Morpho public API (see endpoint below). This is the easiest and most direct way for most applications. Note that only a subset of chains are supported. * **Typescript**: Using Typescript snippet. For Developers expecting to perform offchain computation. * **Smart Contract**: Fetching data directly onchain. This is best for real-time or trustless data. * **SDK**: \[Examples Incoming] Using the Morpho SDKs for pre-built abstractions and faster development. API Endpoint: [api.morpho.org/graphql](https://api.morpho.org/graphql) Review the [Morpho API main rules](/developers/api/get-started/#morpho-api-main-rules) before sending requests. By default, the API returns only the first 100 results and targets the Ethereum network unless you specify otherwise. For each topic below, you'll find short guides for each method (where possible). This avoids redundancy and helps you choose the best approach for your use case. ## Discovery and Listing [#discovery-and-listing] ### Vaults List [#vaults-list] #### Morpho Vaults V2 [#morpho-vaults-v2] ```graphql title="API" query { vaultV2s(first: 1000, where: { chainId_in: [1, 8453] }) { items { address symbol name listed asset { id address decimals } chain { id network } } } } ``` ```typescript import "dotenv/config"; import { createPublicClient, http, PublicClient, parseAbiItem } from "viem"; import { mainnet } from "viem/chains"; export async function createMainnetClient(): Promise { const client = createPublicClient({ chain: mainnet, transport: http(process.env.RPC_URL_MAINNET!, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); return client; } // Morpho Vault V2 Factory contract address const MORPHO_VAULT_V2_FACTORY_ADDRESS = "0xA1D94F746dEfa1928926b84fB2596c06926C0405"; // Block range to search for vault creations const START_BLOCK = 23716940n; const END_BLOCK = 23717000n; // CreateVaultV2 event ABI const CREATE_VAULT_V2_EVENT = parseAbiItem( "event CreateVaultV2(address indexed owner, address indexed asset, bytes32 salt, address indexed newVaultV2)" ); interface VaultV2CreationEvent { newVaultV2: string; owner: string; asset: string; salt: string; blockNumber: bigint; transactionHash: string; } export async function fetchNewVaultV2Creations( client: PublicClient, startBlock: bigint = START_BLOCK, endBlock: bigint = END_BLOCK ): Promise { console.log(`Fetching Vault V2 creations from block ${startBlock} to ${endBlock}...`); // Get logs for CreateVaultV2 events const logs = await client.getLogs({ address: MORPHO_VAULT_V2_FACTORY_ADDRESS, event: CREATE_VAULT_V2_EVENT, fromBlock: startBlock, toBlock: endBlock, }); console.log(`Found ${logs.length} Vault V2 creation(s)`); // Parse and format the logs const vaultCreations: VaultV2CreationEvent[] = logs.map((log) => ({ newVaultV2: log.args.newVaultV2!, owner: log.args.owner!, asset: log.args.asset!, salt: log.args.salt!, blockNumber: log.blockNumber!, transactionHash: log.transactionHash!, })); return vaultCreations; } export function displayVaultV2Creations(vaultCreations: VaultV2CreationEvent[]): void { if (vaultCreations.length === 0) { console.log("No new Vault V2s were created in the specified block range."); return; } console.log("\n=== New MetaMorpho Vault V2s Created ==="); vaultCreations.forEach((vault, index) => { console.log(`\n${index + 1}. Vault V2 Address: ${vault.newVaultV2}`); console.log(` Owner: ${vault.owner}`); console.log(` Asset: ${vault.asset}`); console.log(` Salt: ${vault.salt}`); console.log(` Block: ${vault.blockNumber}`); console.log(` Transaction: ${vault.transactionHash}`); }); } export async function main(): Promise { try { const client = await createMainnetClient(); // Fetch Vault V2 creations in the specified block range const vaultCreations = await fetchNewVaultV2Creations(client, START_BLOCK, END_BLOCK); // Display the results displayVaultV2Creations(vaultCreations); } catch (error) { console.error("Error fetching Vault V2 creations:", error); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ```json { "message": "Fetching Vault V2 creations from block 23716940 to 23717000...", "found": "Found 1 Vault V2 creation(s)", "vaults": [ { "address": "0x5E5b30c5fD07da26d1e515dfEDb1D37C93417652", "owner": "0x46057881E0B9d190920FB823F840B837f65745d5", "asset": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "salt": "0x5dbd6b3d5444d58eb3b873a76f4a26158f4a960b67d87125ffcd305053e16a3a", "blockNumber": "23716959", "transactionHash": "0x73f2c14195c38ca887e44fed4e2bb9619a858ddea7337258d748a3079ef417f0" } ] } ``` This example demonstrates how to: * Listen to `CreateVaultV2` events from the Morpho Vault V2 Factory contract on Ethereum (`0xA1D94F746dEfa1928926b84fB2596c06926C0405`). Factories addresses on resp. chains supported are [here](/get-started/resources/addresses/). * Filter events within a specific block range * Extract all vault creation details from the event logs * Parse and format the results for easy consumption One can listen to all `CreateVaultV2` events emitted from the `MorphoVaultV2Factory` contract. The contracts addresses are [here](/get-started/resources/addresses/). ```solidity /// @notice Emitted when a new Morpho Vault V2 is created. /// @param owner The initial owner of the vault. /// @param asset The address of the underlying asset. /// @param salt The salt used for the vault's CREATE2 address. /// @param newVaultV2 The address of the newly created Vault V2. event CreateVaultV2( address indexed owner, address indexed asset, bytes32 salt, address indexed newVaultV2 ); ``` ## Vault Metrics [#vault-metrics] ### Total Deposits & Assets [#total-deposits--assets] #### Morpho Vaults V2 [#morpho-vaults-v2-1] Easiest to implement - perfect for most applications ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address totalAssets totalAssetsUsd totalSupply liquidity liquidityUsd idleAssetsUsd } } ``` ```graphql query { vaultV2s(first: 100) { items { address totalAssets totalAssetsUsd totalSupply liquidityUsd idleAssetsUsd } } } ``` The following example demonstrates how to: * Connect to the Ethereum mainnet using Viem * Fetch total deposits and assets from a Morpho Vault V2 * Calculate and display formatted vault statistics including total supply, total assets, and USD value * Parse and format results for easy consumption ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns {PublicClient} A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // The address of the Morpho Vault V2 const VAULT_ADDRESS: Address = "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145"; // Minimal ABI for an ERC-4626 compliant vault to get total assets, supply, and the underlying asset. // Vault V2 is ERC-4626 compliant and supports these standard functions. const MINIMAL_VAULT_ABI = parseAbi([ "function totalAssets() external view returns (uint256)", "function totalSupply() external view returns (uint256)", "function asset() external view returns (address)", ]); // Minimal ABI for an ERC-20 token to get its decimals. const MINIMAL_ERC20_ABI = parseAbi([ "function decimals() external view returns (uint8)", ]); interface VaultStats { vaultAddress: Address; totalSupply: bigint; totalAssets: bigint; totalAssetsUsd: number; assetAddress: Address; assetDecimals: number; } /** * Fetches the key statistics for a given Morpho vault. * @param {PublicClient} client - The Viem public client. * @param {Address} vaultAddress - The address of the vault to query. * @returns {Promise} An object containing the vault's stats. */ export async function getVaultStats( client: PublicClient, vaultAddress: Address ): Promise { console.log(`Fetching stats for vault: ${vaultAddress}...`); // Use multicall to fetch vault data in a single RPC request for efficiency const [totalSupply, totalAssets, assetAddress] = await client.multicall({ contracts: [ { address: vaultAddress, abi: MINIMAL_VAULT_ABI, functionName: "totalSupply", }, { address: vaultAddress, abi: MINIMAL_VAULT_ABI, functionName: "totalAssets", }, { address: vaultAddress, abi: MINIMAL_VAULT_ABI, functionName: "asset", }, ], allowFailure: false, }); // Fetch the decimals of the underlying asset (in this case, USDC) const assetDecimals = await client.readContract({ address: assetAddress, abi: MINIMAL_ERC20_ABI, functionName: "decimals", }); // --- USD CONVERSION --- // For this script, we use a manual price for the underlying asset (USDC). // In a production environment, you should replace this with a dynamic price feed. // // HOW TO GET A DYNAMIC PRICE: // 1. Use an onchain oracle (e.g., Chainlink) if your script runs in a smart contract context. // 2. Use the Morpho API's `assetByAddress` query to get `price { usd }`. // Read `price.timestamp` too if freshness matters. // 3. Use a third-party price API like DefiLlama, CoinGecko, or a paid service. // // Since the underlying asset is USDC, its price is pegged to ~$1.00. const underlyingAssetPriceUsd = 1.0; // Format the totalAssets value from a BigInt to a floating-point number const totalAssetsFormatted = parseFloat( formatUnits(totalAssets, assetDecimals) ); // Calculate the total value in USD const totalAssetsUsd = totalAssetsFormatted * underlyingAssetPriceUsd; return { vaultAddress, totalSupply, totalAssets, totalAssetsUsd, assetAddress, assetDecimals, }; } /** * Displays the fetched vault statistics in a clean, readable format. * @param {VaultStats} stats - The vault statistics object. */ export function displayVaultStats(stats: VaultStats): void { console.log("\n✅ --- Morpho Vault Stats --- ✅"); console.log(`\nVault Address: ${stats.vaultAddress}`); console.log(`Underlying Asset: ${stats.assetAddress}`); // The vault's shares (totalSupply) are typically 18 decimals, regardless of the underlying asset's decimals. const totalSupplyFormatted = formatUnits(stats.totalSupply, 18); const totalAssetsFormatted = formatUnits(stats.totalAssets, stats.assetDecimals); console.log("\n--- Totals ---"); console.log( `Total Supply (Shares): ${totalSupplyFormatted} (raw: ${stats.totalSupply})` ); console.log( `Total Assets (${stats.assetDecimals} decimals): ${totalAssetsFormatted} (raw: ${stats.totalAssets})` ); console.log( `Total Assets (USD): $${stats.totalAssetsUsd.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2, })}` ); console.log("\n---------------------------------"); } /** * Main function to run the script. */ export async function main(): Promise { try { const client = createMainnetClient(); const vaultStats = await getVaultStats(client, VAULT_ADDRESS); displayVaultStats(vaultStats); } catch (error) { console.error("An error occurred during script execution:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Fetching stats for vault: 0x04422053aDDbc9bB2759b248B574e3FCA76Bc145... ✅ --- Morpho Vault Stats --- ✅ Vault Address: 0x04422053aDDbc9bB2759b248B574e3FCA76Bc145 Underlying Asset: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --- Totals --- Total Supply (Shares): 406046.668299445014491638 (raw: 406046668299445014491638) Total Assets (6 decimals): 409312.044166 (raw: 409312044166) Total Assets (USD): $409,312.04 --------------------------------- ``` As it is important to accrue interests on the underlying adapters and markets, consider using the full implementation in the [Morpho Vault V2 Snippets](https://github.com/morpho-org/vault-v2/blob/main/README.md) repository for production use. ```solidity title="Raw Contract Calls" /// @notice Returns the total assets deposited into a VaultV2 `vault`. /// @dev This includes both idle assets in the vault and assets allocated to adapters. /// @dev The value is computed by accruing interest and aggregating adapter positions. /// @param vault The address of the VaultV2 vault. /// @return totalAssets The total assets controlled by the vault. function totalDepositVaultV2(address vault) public view returns (uint256 totalAssets) { totalAssets = IVaultV2(vault).totalAssets(); } ``` ### APY (Native + Rewards) [#apy-native--rewards] #### Morpho Vaults V2 [#morpho-vaults-v2-2] ###### Morpho Vaults V2 can be eligible for 2 levels of rewards: [#morpho-vaults-v2-can-be-eligible-for-2-levels-of-rewards] * **Market level rewards:** These are inherited from the underlying Morpho markets where the Morpho Vault V2 allocates assets. * **Vault level rewards:** These are distributed directly to the Morpho Vault V2 itself. Note for Morpho Vault V2: all rewards appearing on the Morpho API are displayed directly under the `rewards` section. ###### APY Components Breakdown: [#apy-components-breakdown] 1. **Native APY:** The base yield earned from the vault's underlying allocations. 2. **Underlying Token Yield:** The yield generated by the underlying token itself (when applicable). For yield-bearing loan assets, this is calculated using daily exchange rate changes queried at 1-day block intervals. Access this via `asset.yield.apr`. 3. **Reward APRs:** Additional incentives visible directly through `rewards`. 4. **Performance Fee Adjustment:** The performance fee is applied only to the Native APY component. 5. **Management Fee Adjustment:** A time-based fee (annual rate, if applicable) deducted from the total assets regardless of performance. The combination of these components results in the Net APY: $$ \text{Net APY}= \text{Native APY} \times (1 - \text{Performance Fee}) + \text{Underlying Token Yield} $$ $$ \phantom{\text{Net APY} =} + \sum \text{Rewards APR} - \text{Management Fee} $$ Where: * Performance Fee is applied **only** to Native APY (as a percentage) * Management Fee is applied to total assets (as an annual rate) * Underlying Token Yield applies only to yield-bearing assets (0 otherwise) * Rewards APR is the sum of all reward incentives ###### Important UI Reference one might find on apps: [#important-ui-reference-one-might-find-on-apps] 1. `avgNetApyExcludingRewards` represents the native APY (6h average vault APY excluding rewards, before deducting fees). 2. `avgNetApy` represents the complete APY (6h average vault APY including rewards, after deducting fees). ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address asset { yield { apr } } avgNetApyExcludingRewards avgNetApy performanceFee managementFee maxRate rewards { asset { address chain { id } } supplyApr } } } ``` ```graphql query { vaultV2s(first: 10) { items { address asset { yield { apr } } avgNetApyExcludingRewards avgNetApy performanceFee managementFee maxRate rewards { asset { address chain { id } } supplyApr } } } } ``` ### Share Price (Token Value) [#share-price-token-value] **Why You Shouldn't Calculate Manually** Computing the exchange rate as `totalAssets / totalSupply` can show false "drops" during fee accruals. This happens because these values update at different times, creating temporary inconsistencies. **How ERC-4626 Calculates Share Price** The standard includes **virtual offsets** to prevent inflation attacks and ensure consistency: * **Virtual Assets**: `+1` added to totalAssets * **Virtual Shares**: `+10^DECIMALS_OFFSET` added to totalSupply * DECIMALS\_OFFSET matches the underlying asset decimals (6 for USDC, 18 for WETH) **Formula** for pricing 1 share (10^18 wei): $$ \text{sharePrice} = \frac{10^{18} \times (\text{totalAssets} + 1)}{\text{totalSupply} + 10^{\text{DECIMALS\_OFFSET}}} $$ The result is in raw underlying asset units. To get the human-readable price, divide by `10^assetDecimals`. **Recommended Methods:** * **API**: Use the `sharePrice` field (already computed correctly) * **Onchain**: Use `convertToAssets(10^18)` instead of manual calculation **Practical Example with USDC Vault** Using real data from a USDC vault (6 decimals): $$ \text{totalAssets: } 473{,}435{,}155{,}471{,}028 \text{ (raw USDC units)} $$ $$ \text{totalSupply: } 427{,}784{,}657{,}006{,}652{,}956{,}819{,}706{,}379 \text{ (raw share units)} $$ Applying the formula: $$ \text{sharePrice} = \frac{10^{18} \times 473{,}435{,}155{,}471{,}029}{427{,}784{,}657{,}006{,}652{,}956{,}819{,}706{,}379 + 10^6} = 1{,}106{,}713 \text{ (raw units)} $$ Converting to human-readable: $$ \frac{1{,}106{,}713}{10^6} = 1.106713 \text{ USDC per share} $$ This matches the API's `sharePrice` field value! #### Morpho Vaults V2 [#morpho-vaults-v2-3] ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { totalAssets totalSupply sharePrice } } ``` ```graphql query { vaultV2s(first: 10) { items { address totalSupply totalAssets sharePrice } } } ``` This example demonstrates how to price a Morpho Vault V2 token using the standard ERC-4626 methodology. Since vault tokens represent a share of the underlying assets, their price appreciates as the vault accrues yield. The script shows how to: * Connect to Ethereum mainnet using Viem. * Use the `convertToAssets` function to determine how many underlying assets correspond to one full vault share token. * Fetch vault and underlying asset details (symbols, decimals) using `multicall` for efficiency. * Format the raw price into a human-readable string (e.g., "1.008 USDC"). * Calculate the USD value of the vault token. ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // The address of the Morpho Vault V2 const VAULT_V2_ADDRESS: Address = "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145"; // Minimal ABIs for an ERC-4626 vault and an ERC-20 token. // Vault V2 is ERC-4626 compliant and supports these standard functions. const VAULT_V2_ABI = parseAbi([ "function convertToAssets(uint256 shares) view returns (uint256 assets)", "function totalAssets() view returns (uint256)", "function totalSupply() view returns (uint256)", "function asset() view returns (address)", "function symbol() view returns (string)", ]); const ERC20_ABI = parseAbi([ "function decimals() view returns (uint8)", "function symbol() view returns (string)", ]); interface VaultPriceInfo { vaultAddress: Address; vaultSymbol: string; sharePriceInAssets: bigint; // Raw value from convertToAssets underlyingAsset: { address: Address; symbol: string; decimals: number; }; formattedPrice: string; // e.g., "1.026 USDC" usdPrice: number; } /** * Calculates the price of a single vault share in terms of its underlying asset. * This function demonstrates the standard ERC-4626 method for pricing shares. * @param client The Viem public client. * @param vaultAddress The address of the Morpho Vault V2. * @returns A promise that resolves to a VaultPriceInfo object. */ export async function calculateVaultTokenPrice( client: PublicClient, vaultAddress: Address ): Promise { console.log(`Calculating token price for vault: ${vaultAddress}...`); // A single vault share token has 18 decimals. const oneShare = 10n ** 18n; // Use multicall to fetch vault and underlying asset data efficiently. const [ assetsPerShare, underlyingAssetAddress, vaultSymbol, totalSupply, totalAssets, ] = await client.multicall({ contracts: [ { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "convertToAssets", args: [oneShare], }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "asset", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "symbol", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "totalSupply", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "totalAssets", }, ], allowFailure: false, }); // Fetch details of the underlying asset (e.g., USDC). const [underlyingDecimals, underlyingSymbol] = await client.multicall({ contracts: [ { address: underlyingAssetAddress, abi: ERC20_ABI, functionName: "decimals", }, { address: underlyingAssetAddress, abi: ERC20_ABI, functionName: "symbol", }, ], allowFailure: false, }); // For USD conversion, a dynamic price feed is recommended. // For this example with USDC, we can hardcode the price as ~$1.0. const underlyingAssetPriceUsd = 1.0; const formattedAssetsPerShare = formatUnits( assetsPerShare, underlyingDecimals ); const usdPrice = parseFloat(formattedAssetsPerShare) * underlyingAssetPriceUsd; return { vaultAddress, vaultSymbol, sharePriceInAssets: assetsPerShare, underlyingAsset: { address: underlyingAssetAddress, symbol: underlyingSymbol, decimals: underlyingDecimals, }, formattedPrice: `${formattedAssetsPerShare} ${underlyingSymbol}`, usdPrice, }; } /** * Displays the calculated vault token price information in a clean format. * @param priceInfo The object containing the price information. */ export function displayVaultTokenPrice(priceInfo: VaultPriceInfo): void { console.log("\n✅ --- Morpho Vault V2 Token Price --- ✅"); console.log(`\nVault: ${priceInfo.vaultSymbol} (${priceInfo.vaultAddress})`); console.log( `Underlying Asset: ${priceInfo.underlyingAsset.symbol} (${priceInfo.underlyingAsset.address})` ); console.log("\n--- Price ---"); console.log(`1 ${priceInfo.vaultSymbol} = ${priceInfo.formattedPrice}`); console.log( `Price (raw units): ${ priceInfo.sharePriceInAssets } (with ${priceInfo.underlyingAsset.decimals} decimals)` ); console.log( `Price (USD): $${priceInfo.usdPrice.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 4, })}` ); console.log("\n----------------------------------------"); } /** * Main function to orchestrate the script execution. */ export async function main(): Promise { try { const client = createMainnetClient(); const priceInfo = await calculateVaultTokenPrice(client, VAULT_V2_ADDRESS); displayVaultTokenPrice(priceInfo); } catch (error) { console.error("An error occurred during script execution:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Calculating token price for vault: 0x04422053aDDbc9bB2759b248B574e3FCA76Bc145... --- Morpho Vault V2 Token Price --- Vault: kUSDC (0x04422053aDDbc9bB2759b248B574e3FCA76Bc145) Underlying Asset: USDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) --- Price --- 1 kUSDC = 1.008451 USDC Price (raw units): 1008451 (with 6 decimals) Price (USD): $1.0085 ---------------------------------------- ``` ```solidity // Get the price of 1 vault token in underlying asset units // Works for Morpho Vault V2 (ERC-4626 compliant) uint256 oneShare = 10**IERC20Metadata(vault).decimals(); uint256 priceInAssets = IERC4626(vault).convertToAssets(oneShare); // Alternative: Calculate manually using total supply and total assets uint256 totalSupply = IERC20(vault).totalSupply(); uint256 totalAssets = IERC4626(vault).totalAssets(); uint256 priceManual = (totalAssets * oneShare) / totalSupply; // Both methods return the same result: // Price = how many underlying assets 1 vault token is worth ``` **Key Functions:** * `convertToAssets(shares)`: ERC-4626 standard function that converts vault shares to underlying assets * `totalAssets()`: Total underlying assets managed by the vault * `totalSupply()`: Total vault tokens in circulation **Price Formula:** `Price = totalAssets / totalSupply` The `convertToAssets` approach is recommended as it handles edge cases and follows the ERC-4626 standard. ## Allocation & Strategy [#allocation--strategy] ### Current Allocations [#current-allocations] #### Morpho Vaults V2 [#morpho-vaults-v2-4] **Vaults V2 allocate through adapters, not directly to markets.** Each adapter reports its current holdings via `realAssets()`, and the vault aggregates these values to calculate total allocations. Vaults V2 can allocate to Morpho markets and any future approved protocol with an enabled adapter. ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address totalAssetsUsd totalAssets totalSupply idleAssets idleAssetsUsd adapters(first: 20) { items { __typename address assets assetsUsd type ... on MorphoMarketV1Adapter { positions(first: 50) { items { market { marketId collateralAsset { symbol } loanAsset { symbol } } state { supplyAssets supplyAssetsUsd } } } } ... on MetaMorphoAdapter { metaMorpho { address name asset { symbol } } } ... on MorphoVaultV2Adapter { innerVault { address name asset { symbol } } } } } } } ``` ```graphql query { vaultV2s(first: 10) { items { address totalAssetsUsd totalAssets totalSupply idleAssets idleAssetsUsd adapters(first: 5) { items { __typename address assets assetsUsd type ... on MorphoMarketV1Adapter { positions(first: 10) { items { market { marketId collateralAsset { symbol } loanAsset { symbol } } state { supplyAssets supplyAssetsUsd } } } } ... on MetaMorphoAdapter { metaMorpho { address name asset { symbol } } } ... on MorphoVaultV2Adapter { innerVault { address name asset { symbol } } } } } } } } ``` This example demonstrates how to: * Fetch all adapters from the Vault V2 and detect their types * Retrieve allocations from each adapter * Calculate idle assets held directly by the vault * Determine allocation amounts and percentages across all destinations * Display formatted allocation results with adapter breakdown * Use multicall for efficient batch operations ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, keccak256, encodeAbiParameters, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2 }), }); } const VAULT_V2_ADDRESS: Address = "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145"; const MORPHO_BLUE_ADDRESS: Address = "0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb"; // Minimal ABIs with only the functions we need for this script. const VAULT_V2_ABI = parseAbi([ "function adaptersLength() external view returns (uint256)", "function adapters(uint256) external view returns (address)", "function asset() external view returns (address)", "function totalAssets() external view returns (uint256)", ]); const ADAPTER_ABI = parseAbi([ "function realAssets() external view returns (uint256)", "function morpho() external view returns (address)", "function morphoVaultV1() external view returns (address)", "function marketParamsListLength() external view returns (uint256)", "function marketParamsList(uint256) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", ]); const MORPHO_BLUE_ABI = parseAbi([ "function market(bytes32 id) external view returns (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee)", "function idToMarketParams(bytes32 id) external view returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv)", "function position(bytes32 id, address user) external view returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral)", ]); const IRM_ABI = parseAbi([ "function borrowRateView((address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) marketParams, (uint128 totalSupplyAssets, uint128 totalSupplyShares, uint128 totalBorrowAssets, uint128 totalBorrowShares, uint128 lastUpdate, uint128 fee) market) external view returns (uint256)", ]); const MINIMAL_ERC20_ABI = parseAbi([ "function decimals() view returns (uint8)", "function balanceOf(address) view returns (uint256)", ]); const MORPHO_VAULT_V1_ABI = parseAbi([ "function asset() external view returns (address)", "function balanceOf(address) external view returns (uint256)", "function convertToAssets(uint256) external view returns (uint256)", ]); type MarketParams = { loanToken: Address; collateralToken: Address; oracle: Address; irm: Address; lltv: bigint; }; type MarketState = { totalSupplyAssets: bigint; totalSupplyShares: bigint; totalBorrowAssets: bigint; totalBorrowShares: bigint; lastUpdate: bigint; fee: bigint; }; enum AdapterType { MorphoMarketV1 = "MorphoMarketV1Adapter", MorphoVaultV1 = "MorphoVaultV1Adapter", Unknown = "Unknown" } interface AllocationDestination { type: "Market" | "Vault" | "Idle"; id?: `0x${string}`; // Market ID for markets address?: Address; // Vault address for vaults allocatedAssets: bigint; percentage: number; } interface AdapterAllocation { adapterAddress: Address; adapterType: AdapterType; destinations: AllocationDestination[]; totalAssets: bigint; } interface VaultV2Allocations { idle: AllocationDestination; adapters: AdapterAllocation[]; totalAllocated: bigint; } const WAD = 10n ** 18n; const VIRTUAL_ASSETS = 1n; // To avoid division by zero const VIRTUAL_SHARES = 10n ** 6n; // To avoid division by zero const wMulDown = (x: bigint, y: bigint): bigint => (x * y) / WAD; const wTaylorCompounded = (x: bigint, n: bigint): bigint => { const firstTerm = x * n; const secondTerm = (firstTerm * firstTerm) / (2n * WAD); const thirdTerm = (secondTerm * firstTerm) / (3n * WAD); return firstTerm + secondTerm + thirdTerm; }; /** Converts a share amount to its corresponding asset amount, rounding down. */ const toAssetsDown = (shares: bigint, totalAssets: bigint, totalShares: bigint): bigint => { if (totalShares === 0n) return shares; return (shares * (totalAssets + VIRTUAL_ASSETS)) / (totalShares + VIRTUAL_SHARES); }; /** Detects the adapter type by trying to call specific functions */ async function detectAdapterType(client: PublicClient, adapterAddress: Address): Promise { try { // Try to call morphoVaultV1() - if it succeeds, it's a MorphoVaultV1Adapter await client.readContract({ address: adapterAddress, abi: ADAPTER_ABI, functionName: "morphoVaultV1", }); return AdapterType.MorphoVaultV1; } catch { try { // Try to call morpho() - if it succeeds, it's a MorphoMarketV1AdapterV2 await client.readContract({ address: adapterAddress, abi: ADAPTER_ABI, functionName: "morpho", }); return AdapterType.MorphoMarketV1; } catch { return AdapterType.Unknown; } } } /** Fetches allocations from a MorphoMarketV1AdapterV2 */ async function getMarketAdapterAllocations( client: PublicClient, adapterAddress: Address, blockTimestamp: bigint ): Promise { const marketListLength = await client.readContract({ address: adapterAddress, abi: ADAPTER_ABI, functionName: "marketParamsListLength", }) as bigint; if (marketListLength === 0n) { return []; } const calls = Array.from({ length: Number(marketListLength) }, (_, i) => ({ address: adapterAddress, abi: ADAPTER_ABI, functionName: "marketParamsList" as const, args: [BigInt(i)], })); const marketParamsList = await client.multicall({ contracts: calls, allowFailure: false }); const destinations: AllocationDestination[] = []; for (const marketParamsResult of marketParamsList) { const params: MarketParams = { loanToken: marketParamsResult[0], collateralToken: marketParamsResult[1], oracle: marketParamsResult[2], irm: marketParamsResult[3], lltv: marketParamsResult[4], }; // Calculate market ID const marketId = keccak256( encodeAbiParameters( [ { type: "address" }, { type: "address" }, { type: "address" }, { type: "address" }, { type: "uint256" }, ], [params.loanToken, params.collateralToken, params.oracle, params.irm, params.lltv] ) ) as `0x${string}`; // Get adapter's position in this market const [marketState, { 0: supplyShares }] = await Promise.all([ client.readContract({ address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "market", args: [marketId], }), client.readContract({ address: MORPHO_BLUE_ADDRESS, abi: MORPHO_BLUE_ABI, functionName: "position", args: [marketId, adapterAddress], }), ]); const allocatedAssets = toAssetsDown( supplyShares as bigint, marketState[0] as bigint, marketState[1] as bigint ); if (allocatedAssets > 0n) { destinations.push({ type: "Market", id: marketId, allocatedAssets, percentage: 0, // Will be calculated later }); } } return destinations; } /** Fetches allocations from a MorphoVaultV1Adapter */ async function getVaultAdapterAllocations( client: PublicClient, adapterAddress: Address ): Promise { const morphoVaultV1Address = await client.readContract({ address: adapterAddress, abi: ADAPTER_ABI, functionName: "morphoVaultV1", }) as Address; // Get the adapter's balance in the Morpho Vault V1 const shares = await client.readContract({ address: morphoVaultV1Address, abi: MORPHO_VAULT_V1_ABI, functionName: "balanceOf", args: [adapterAddress], }) as bigint; // Convert shares to assets const allocatedAssets = await client.readContract({ address: morphoVaultV1Address, abi: MORPHO_VAULT_V1_ABI, functionName: "convertToAssets", args: [shares], }) as bigint; if (allocatedAssets > 0n) { return [ { type: "Vault", address: morphoVaultV1Address, allocatedAssets, percentage: 0, // Will be calculated later }, ]; } return []; } /** * Fetches all allocations for a Vault V2 * @returns VaultV2Allocations object containing idle assets and adapter allocations */ export async function getVaultV2Allocations( client: PublicClient, vaultAddress: Address ): Promise { // Get idle assets (vault's direct balance) const [underlyingAssetAddress, idleAssets, adaptersLength] = await client.multicall({ contracts: [ { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "asset" }, { address: vaultAddress, abi: MINIMAL_ERC20_ABI, functionName: "balanceOf", args: [vaultAddress], } as any, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "adaptersLength" }, ], allowFailure: false, }); const adapterCount = Number(adaptersLength); if (adapterCount === 0) { console.log("Vault has no adapters. Only idle assets available."); return { idle: { type: "Idle", allocatedAssets: idleAssets as bigint, percentage: 100, }, adapters: [], totalAllocated: idleAssets as bigint, }; } // Fetch all adapter addresses const adapterCalls = Array.from({ length: adapterCount }, (_, i) => ({ address: vaultAddress, abi: VAULT_V2_ABI, functionName: "adapters" as const, args: [BigInt(i)], })); const adapterAddresses = (await client.multicall({ contracts: adapterCalls, allowFailure: false, })) as Address[]; console.log(`Found ${adapterAddresses.length} adapter(s). Fetching allocations...`); const block = await client.getBlock({ blockTag: "latest" }); const adapterAllocations: AdapterAllocation[] = []; // Process each adapter for (const adapterAddress of adapterAddresses) { const adapterType = await detectAdapterType(client, adapterAddress); let destinations: AllocationDestination[] = []; if (adapterType === AdapterType.MorphoMarketV1) { destinations = await getMarketAdapterAllocations(client, adapterAddress, block.timestamp); } else if (adapterType === AdapterType.MorphoVaultV1) { destinations = await getVaultAdapterAllocations(client, adapterAddress); } const totalAssets = destinations.reduce((sum, d) => sum + d.allocatedAssets, 0n); if (totalAssets > 0n || destinations.length > 0) { adapterAllocations.push({ adapterAddress, adapterType, destinations, totalAssets, }); } } // Calculate total allocated across all destinations const totalInAdapters = adapterAllocations.reduce((sum, a) => sum + a.totalAssets, 0n); const totalAllocated = (idleAssets as bigint) + totalInAdapters; // Calculate percentages const idleAllocation: AllocationDestination = { type: "Idle", allocatedAssets: idleAssets as bigint, percentage: totalAllocated > 0n ? Number(((idleAssets as bigint) * 10000n) / totalAllocated) / 100 : 0, }; // Update percentages for all adapter destinations for (const adapter of adapterAllocations) { for (const destination of adapter.destinations) { destination.percentage = totalAllocated > 0n ? Number((destination.allocatedAssets * 10000n) / totalAllocated) / 100 : 0; } } return { idle: idleAllocation, adapters: adapterAllocations, totalAllocated, }; } /** * Displays the vault's allocations in a formatted table, including idle assets and adapter destinations. */ export async function displayAllocations(client: PublicClient, vaultAddress: Address, allocations: VaultV2Allocations): Promise { const underlyingAssetAddress = await client.readContract({address: vaultAddress, abi: VAULT_V2_ABI, functionName: "asset"}); const underlyingAssetDecimals = await client.readContract({address: underlyingAssetAddress, abi: MINIMAL_ERC20_ABI, functionName: "decimals"}); console.log("\n✅ --- Morpho Vault V2 Allocations --- ✅"); console.log(`\nVault Address: ${vaultAddress}`); console.log(`Total Allocated: ${formatUnits(allocations.totalAllocated, underlyingAssetDecimals)}`); console.log("--------------------------------------------------------------------------------------------------"); // Display idle assets console.log("\n🏦 Idle Assets:"); console.log("--------------------------------------------------------------------------------------------------"); const formattedIdle = formatUnits(allocations.idle.allocatedAssets, underlyingAssetDecimals); console.log( `Idle (in vault)`.padEnd(68), `${parseFloat(formattedIdle).toLocaleString('en-US', {maximumFractionDigits: 2}).padStart(20)}`, `${allocations.idle.percentage.toFixed(2).padStart(10)}%` ); // Display adapter allocations if (allocations.adapters.length === 0) { console.log("\n📦 Adapters: No adapters configured for this vault."); } else { console.log(`\n📦 Adapters (${allocations.adapters.length}):`); for (const adapter of allocations.adapters) { console.log("--------------------------------------------------------------------------------------------------"); console.log(`\nAdapter: ${adapter.adapterAddress}`); console.log(`Type: ${adapter.adapterType}`); console.log(`Total in Adapter: ${formatUnits(adapter.totalAssets, underlyingAssetDecimals)}`); if (adapter.destinations.length === 0) { console.log(" No allocations in this adapter."); } else { console.log("\n Destinations:"); console.log(" " + "-".repeat(94)); console.log(" " + "Destination".padEnd(68), "Allocation".padEnd(20), "Percentage"); console.log(" " + "-".repeat(94)); for (const dest of adapter.destinations) { const formattedAssets = formatUnits(dest.allocatedAssets, underlyingAssetDecimals); let destinationLabel = ""; if (dest.type === "Market") { destinationLabel = `Market: ${dest.id}`; } else if (dest.type === "Vault") { destinationLabel = `Vault: ${dest.address}`; } console.log( ` ${destinationLabel.padEnd(68)}`, `${parseFloat(formattedAssets).toLocaleString('en-US', {maximumFractionDigits: 2}).padStart(20)}`, `${dest.percentage.toFixed(2).padStart(10)}%` ); } } } } console.log("--------------------------------------------------------------------------------------------------"); } /** * Main function to run the script. */ export async function main(): Promise { try { const client = createMainnetClient(); const allocations = await getVaultV2Allocations(client, VAULT_V2_ADDRESS); await displayAllocations(client, VAULT_V2_ADDRESS, allocations); } catch (error) { console.error("An error occurred during script execution:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Found 1 adapter(s). Fetching allocations... ✅ --- Morpho Vault V2 Allocations --- ✅ Vault Address: 0x04422053aDDbc9bB2759b248B574e3FCA76Bc145 Total Allocated: 380947.871852 -------------------------------------------------------------------------------------------------- 🏦 Idle Assets: -------------------------------------------------------------------------------------------------- Idle (in vault) 0 0.00% 📦 Adapters (1): -------------------------------------------------------------------------------------------------- Adapter: 0x0c2D17F72965944e7755C992E052b725Ab5AA5Ea Type: MorphoMarketV1AdapterV2 Total in Adapter: 380947.871852 Destinations: ---------------------------------------------------------------------------------------------- Destination Allocation Percentage ---------------------------------------------------------------------------------------------- Vault: 0x6C26793c7F1e2785c09b460676e797b716f0Bc8E 380,947.87 100.00% -------------------------------------------------------------------------------------------------- ``` ## Configuration & Governance [#configuration--governance] ### Vault Parameters [#vault-parameters] #### Morpho Vaults V2 [#morpho-vaults-v2-5] ```graphql query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { address name listed metadata { description image } allocators { allocator { address } } owner { address } curators { items { addresses { address } } } sentinels { sentinel { address } } timelocks { duration selector functionName } } } ``` ```graphql query { vaultV2s(first: 10) { items { address name listed metadata { description image } allocators { allocator { address } } owner { address } curators { items { addresses { address } } } sentinels { sentinel { address } } timelocks { duration selector functionName } } } } ``` This example demonstrates how to retrieve comprehensive Vault V2 configuration including: * Basic vault information (name, symbol, decimals) * Asset details and financial metrics (totalAssets, totalSupply, fees) * Governance roles (owner, curator) * Adapter configuration (adaptersLength, adapters, adapterRegistry, liquidityAdapter) * Vault gates (receiveAssetsGate, sendSharesGate, receiveSharesGate) * Timelock settings for all critical functions (addAdapter, removeAdapter, setAdapterRegistry, etc.) * Abdication status for each function * Max rate and fee configuration ```typescript import "dotenv/config"; import { createPublicClient, http, formatUnits, PublicClient, parseAbi, Address, } from "viem"; import { mainnet } from "viem/chains"; /** * Creates and configures a Viem Public Client for the Ethereum mainnet. * It reads the RPC URL from the .env file. * @returns A configured Viem public client. */ export function createMainnetClient(): PublicClient { const rpcUrl = process.env.RPC_URL_MAINNET; if (!rpcUrl) { throw new Error( "RPC_URL_MAINNET is not set in the .env file. Please add it." ); } return createPublicClient({ chain: mainnet, transport: http(rpcUrl, { retryCount: 2, batch: { batchSize: 100, wait: 200, }, }), batch: { multicall: { batchSize: 2048, wait: 100, }, }, }); } // The address of the Morpho Vault V2 const VAULT_V2_ADDRESS: Address = "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145"; // Morpho Vault V2 ABI for configuration retrieval const VAULT_V2_ABI = parseAbi([ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function asset() view returns (address)", "function totalAssets() view returns (uint256)", "function totalSupply() view returns (uint256)", "function owner() view returns (address)", "function curator() view returns (address)", "function adaptersLength() view returns (uint256)", "function adapters(uint256) view returns (address)", "function adapterRegistry() view returns (address)", "function liquidityAdapter() view returns (address)", "function timelock(bytes4) view returns (uint256)", "function abdicated(bytes4) view returns (bool)", "function receiveAssetsGate() view returns (address)", "function sendSharesGate() view returns (address)", "function receiveSharesGate() view returns (address)", "function maxRate() view returns (uint256)", "function performanceFee() view returns (uint256)", "function managementFee() view returns (uint256)", ]); const ERC20_ABI = parseAbi([ "function name() view returns (string)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", ]); // Function selectors for timelock queries const FUNCTION_SELECTORS = { setReceiveAssetsGate: "0x04dbf0ce", addAdapter: "0x60d54d41", increaseRelativeCap: "0x2438525b", setReceiveSharesGate: "0x2cb19f98", setForceDeallocatePenalty: "0x3e9d2ac7", increaseTimelock: "0x47966291", removeAdapter: "0x585cd34b", setSendSharesGate: "0xc21ad028", increaseAbsoluteCap: "0xf6f98fd5", setAdapterRegistry: "0x5b34b823", } as const; const FUNCTION_NAMES: Record = { "0x04dbf0ce": "setReceiveAssetsGate", "0x60d54d41": "addAdapter", "0x2438525b": "increaseRelativeCap", "0x2cb19f98": "setReceiveSharesGate", "0x3e9d2ac7": "setForceDeallocatePenalty", "0x47966291": "increaseTimelock", "0x585cd34b": "removeAdapter", "0xc21ad028": "setSendSharesGate", "0xf6f98fd5": "increaseAbsoluteCap", "0x5b34b823": "setAdapterRegistry", }; interface TimelockInfo { selector: string; functionName: string; duration: number; isAbdicated: boolean; } interface VaultV2Configuration { // Basic Info address: Address; name: string; symbol: string; decimals: number; // Asset Info asset: { address: Address; name: string; symbol: string; decimals: number; }; // Financial Info totalAssets: bigint; totalSupply: bigint; performanceFee: number; // As percentage managementFee: number; // As percentage maxRate: bigint; // Governance owner: Address; curator: Address; // Adapters adaptersLength: number; adapters: Address[]; adapterRegistry: Address; liquidityAdapter: Address; // Gates receiveAssetsGate: Address; sendSharesGate: Address; receiveSharesGate: Address; // Timelocks timelocks: TimelockInfo[]; } /** * Helper function to handle zero address cases */ function handleZeroAddress(address: Address): string { return address === "0x0000000000000000000000000000000000000000" ? "Not set (0x0)" : address; } /** * Helper function to format timelock duration */ function formatTimelock(seconds: number): string { if (seconds === 0) return "0s (No timelock)"; const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (days > 0) return `${days}d ${hours}h ${minutes}m`; if (hours > 0) return `${hours}h ${minutes}m`; if (minutes > 0) return `${minutes}m`; return `${seconds}s`; } /** * Retrieves comprehensive Vault V2 configuration information. * @param client The Viem public client. * @param vaultAddress The address of the Morpho Vault V2. * @returns A promise that resolves to a VaultV2Configuration object. */ export async function getVaultV2Configuration( client: PublicClient, vaultAddress: Address ): Promise { console.log(`Fetching configuration for Vault V2: ${vaultAddress}...`); // First multicall to get basic vault information const [ name, symbol, decimals, assetAddress, totalAssets, totalSupply, owner, curator, adaptersLength, adapterRegistry, liquidityAdapter, receiveAssetsGate, sendSharesGate, receiveSharesGate, maxRate, performanceFee, managementFee, ] = await client.multicall({ contracts: [ { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "name" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "symbol" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "decimals" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "asset" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "totalAssets", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "totalSupply", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "owner" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "curator" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "adaptersLength", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "adapterRegistry", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "liquidityAdapter", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "receiveAssetsGate", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "sendSharesGate", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "receiveSharesGate", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "maxRate" }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "performanceFee", }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "managementFee", }, ], allowFailure: false, }); // Fetch adapter addresses const adaptersLengthNum = Number(adaptersLength); const adapters: Address[] = []; if (adaptersLengthNum > 0) { const adapterCalls = Array.from({ length: adaptersLengthNum }, (_, i) => ({ address: vaultAddress, abi: VAULT_V2_ABI, functionName: "adapters" as const, args: [BigInt(i)], })); const adapterResults = await client.multicall({ contracts: adapterCalls, allowFailure: false, }); adapters.push(...(adapterResults as Address[])); } // Fetch asset information const [assetName, assetSymbol, assetDecimals] = await client.multicall({ contracts: [ { address: assetAddress, abi: ERC20_ABI, functionName: "name" }, { address: assetAddress, abi: ERC20_ABI, functionName: "symbol" }, { address: assetAddress, abi: ERC20_ABI, functionName: "decimals" }, ], allowFailure: false, }); // Fetch timelock information for all critical functions const timelocks: TimelockInfo[] = []; for (const [selector, functionName] of Object.entries(FUNCTION_NAMES)) { const [duration, isAbdicated] = await client.multicall({ contracts: [ { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "timelock", args: [selector as `0x${string}`], }, { address: vaultAddress, abi: VAULT_V2_ABI, functionName: "abdicated", args: [selector as `0x${string}`], }, ], allowFailure: false, }); timelocks.push({ selector, functionName, duration: Number(duration), isAbdicated: Boolean(isAbdicated), }); } // Calculate fees as percentages const performanceFeePercent = Number(formatUnits(performanceFee, 18)) * 100; const managementFeePercent = Number(formatUnits(managementFee, 18)) * 100; return { address: vaultAddress, name, symbol, decimals, asset: { address: assetAddress, name: assetName, symbol: assetSymbol, decimals: assetDecimals, }, totalAssets, totalSupply, performanceFee: performanceFeePercent, managementFee: managementFeePercent, maxRate, owner, curator, adaptersLength: adaptersLengthNum, adapters, adapterRegistry, liquidityAdapter, receiveAssetsGate, sendSharesGate, receiveSharesGate, timelocks, }; } /** * Displays the Vault V2 configuration in a formatted manner. */ export function displayVaultV2Configuration( config: VaultV2Configuration ): void { console.log("\n✅ --- Morpho Vault V2 Configuration --- ✅"); console.log("\n--- Basic Information ---"); console.log(`Vault Name: ${config.name}`); console.log(`Vault Symbol: ${config.symbol}`); console.log(`Vault Address: ${config.address}`); console.log(`Vault Decimals: ${config.decimals}`); console.log("\n--- Asset Information ---"); console.log(`Asset Name: ${config.asset.name}`); console.log(`Asset Symbol: ${config.asset.symbol}`); console.log(`Asset Address: ${config.asset.address}`); console.log(`Asset Decimals: ${config.asset.decimals}`); console.log("\n--- Financial Information ---"); console.log( `Total Assets: ${formatUnits(config.totalAssets, config.asset.decimals)} ${config.asset.symbol}` ); console.log( `Total Supply: ${formatUnits(config.totalSupply, config.decimals)} ${config.symbol}` ); console.log( `Performance Fee: ${config.performanceFee.toFixed(4)}% (annual)` ); console.log( `Management Fee: ${config.managementFee.toFixed(4)}% (annual)` ); // Calculate and display max rate as APR const maxRateNumber = Number(config.maxRate); const secondsPerYear = 31557600; // 365.25 days const maxRateAPR = maxRateNumber > 0 ? ((maxRateNumber / 1e18) * secondsPerYear * 100).toFixed(2) : "0"; console.log(`Max Rate: ${maxRateAPR}% APR`); console.log("\n--- Governance ---"); console.log(`Owner: ${config.owner}`); console.log(`Curator: ${handleZeroAddress(config.curator)}`); console.log("\n--- Adapters ---"); console.log(`Adapters Length: ${config.adaptersLength}`); console.log(`Adapter Registry: ${config.adapterRegistry}`); console.log(`Liquidity Adapter: ${config.liquidityAdapter}`); if (config.adapters.length > 0) { console.log(`Configured Adapters:`); config.adapters.forEach((adapter, i) => { console.log(` ${i + 1}. ${adapter}`); }); } console.log("\n--- Vault Gates ---"); console.log( `Receive Assets Gate: ${handleZeroAddress(config.receiveAssetsGate)}` ); console.log(`Send Shares Gate: ${handleZeroAddress(config.sendSharesGate)}`); console.log( `Receive Shares Gate: ${handleZeroAddress(config.receiveSharesGate)}` ); console.log("\n--- Timelocks & Abdications ---"); console.log("Function".padEnd(30), "Duration".padEnd(20), "Abdicated"); console.log("-".repeat(70)); config.timelocks.forEach((tl) => { console.log( tl.functionName.padEnd(30), formatTimelock(tl.duration).padEnd(20), tl.isAbdicated ? "Yes" : "No" ); }); console.log("\n----------------------------------------"); } /** * Main function to run the script. */ export async function main(): Promise { try { const client = createMainnetClient(); // Get Vault V2 configuration const config = await getVaultV2Configuration(client, VAULT_V2_ADDRESS); displayVaultV2Configuration(config); } catch (error) { console.error("An error occurred during script execution:", error); process.exit(1); } } // Run the script if executed directly if (require.main === module) { main(); } ``` **Example Output:** ``` Fetching configuration for Vault V2: 0x04422053aDDbc9bB2759b248B574e3FCA76Bc145... --- Morpho Vault V2 Configuration --- --- Basic Information --- Vault Name: Keyrock USDC Vault Symbol: kUSDC Vault Address: 0x04422053aDDbc9bB2759b248B574e3FCA76Bc145 Vault Decimals: 18 --- Asset Information --- Asset Name: USD Coin Asset Symbol: USDC Asset Address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 Asset Decimals: 6 --- Financial Information --- Total Assets: 380908.53096 USDC Total Supply: 377706.886328436653684714 kUSDC Performance Fee: 0.0000% (annual) Management Fee: 0.0000% (annual) Max Rate: 15.01% APR --- Governance --- Owner: 0xbA75546ACD56b3a9142f94F179b03970eE4283Fd Curator: 0xbA75546ACD56b3a9142f94F179b03970eE4283Fd --- Adapters --- Adapters Length: 1 Adapter Registry: 0x3696c5eAe4a7Ffd04Ea163564571E9CD8Ed9364e Liquidity Adapter: 0x0c2D17F72965944e7755C992E052b725Ab5AA5Ea Configured Adapters: 1. 0x0c2D17F72965944e7755C992E052b725Ab5AA5Ea --- Vault Gates --- Receive Assets Gate: Not set (0x0) Send Shares Gate: Not set (0x0) Receive Shares Gate: Not set (0x0) --- Timelocks & Abdications --- Function Duration Abdicated ---------------------------------------------------------------------- setReceiveAssetsGate 7d 0h 0m No addAdapter 3d 0h 0m No increaseRelativeCap 3d 0h 0m No setReceiveSharesGate 7d 0h 0m No setForceDeallocatePenalty 3d 0h 0m No increaseTimelock 7d 0h 0m No removeAdapter 7d 0h 0m No setSendSharesGate 7d 0h 0m No increaseAbsoluteCap 3d 0h 0m No setAdapterRegistry 0s (No timelock) Yes ---------------------------------------- ``` ```solidity // Basic vault information string memory vaultName = IMorphoVaultV2(vault).name(); string memory vaultSymbol = IMorphoVaultV2(vault).symbol(); uint8 vaultDecimals = IMorphoVaultV2(vault).decimals(); address assetAddress = IMorphoVaultV2(vault).asset(); // Financial metrics uint256 totalAssets = IMorphoVaultV2(vault).totalAssets(); uint256 totalSupply = IMorphoVaultV2(vault).totalSupply(); uint256 performanceFee = IMorphoVaultV2(vault).performanceFee(); // In WAD (18 decimals) uint256 managementFee = IMorphoVaultV2(vault).managementFee(); // In WAD (18 decimals) uint256 maxRate = IMorphoVaultV2(vault).maxRate(); // Max rate in WAD per second // Governance roles address owner = IMorphoVaultV2(vault).owner(); address curator = IMorphoVaultV2(vault).curator(); // Adapter configuration uint256 adaptersLength = IMorphoVaultV2(vault).adaptersLength(); address adapterRegistry = IMorphoVaultV2(vault).adapterRegistry(); address liquidityAdapter = IMorphoVaultV2(vault).liquidityAdapter(); // Get specific adapter by index for (uint256 i = 0; i < adaptersLength; i++) { address adapter = IMorphoVaultV2(vault).adapters(i); // Process adapter... } // Vault gates address receiveAssetsGate = IMorphoVaultV2(vault).receiveAssetsGate(); address sendSharesGate = IMorphoVaultV2(vault).sendSharesGate(); address receiveSharesGate = IMorphoVaultV2(vault).receiveSharesGate(); // Timelock and abdication for specific function bytes4 functionSelector = bytes4(keccak256("addAdapter(address,uint256,uint256)")); uint256 timelockDuration = IMorphoVaultV2(vault).timelock(functionSelector); bool isAbdicated = IMorphoVaultV2(vault).abdicated(functionSelector); ``` **Key Functions:** * **Basic Info**: `name()`, `symbol()`, `decimals()`, `asset()` * **Financial**: `totalAssets()`, `totalSupply()`, `performanceFee()`, `managementFee()`, `maxRate()` * **Roles**: `owner()`, `curator()` * **Adapters**: `adaptersLength()`, `adapters(index)`, `adapterRegistry()`, `liquidityAdapter()` * **Gates**: `receiveAssetsGate()`, `sendSharesGate()`, `receiveSharesGate()` * **Timelocks**: `timelock(bytes4 selector)`, `abdicated(bytes4 selector)` **Important Notes:** * Vault V2 uses adapters for asset allocation instead of direct market queues * Timelocks are function-specific, identified by their 4-byte selector * Abdication means a function's control has been permanently given up (timelock set to infinity) * High-risk functions typically have 7-day timelocks, medium-risk have 3-day timelocks * Gates control access to deposit/withdrawal/transfer operations (address(0) means no gate) ### Pending Actions [#pending-actions] All governance/configuration changes go through a **submit → (wait) → accept/revoke** lifecycle enforced by the timelock. The three events to watch are `Submit`, `Revoke`, and `Accept`. #### Monitoring & Alerts [#monitoring--alerts] To get notified when pending actions are submitted, accepted, or revoked, you have two options: 1. **Build your own notification system** leveraging the query below (API, TypeScript/viem, or from the smart contract). 2. **Off-the-shelf threat monitoring with [Hypernative](https://www.hypernative.io/blog/morpho-integrates-hypernative-to-secure-onchain-lending-for-professional-investors)**. Offering out-of-the-box threat/monitoring alerts for Morpho vaults. #### Morpho Vaults V2 [#morpho-vaults-v2-6] ```graphql query VaultV2PendingActions { vaultV2ByAddress( address: "VAULT_ADDRESS" chainId: 1 ) { address name # pending timelocked actions pendingConfigs(first: 100) { items { validAt functionName txHash decodedData { __typename ... on VaultV2SetIsAllocatorPendingData { isAllocator account { address } } ... on VaultV2SetReceiveSharesGatePendingData { receiveSharesGate } ... on VaultV2SetSendSharesGatePendingData { sendSharesGate } ... on VaultV2SetReceiveAssetsGatePendingData { receiveAssetsGate } ... on VaultV2SetSendAssetsGatePendingData { sendAssetsGate } ... on VaultV2SetAdapterRegistryPendingData { adapterRegistry } ... on VaultV2AdapterPendingData { adapterAddress } ... on VaultV2TimelockPendingData { timelock selector functionName } ... on VaultV2SetPerformanceFeePendingData { performanceFee } ... on VaultV2SetManagementFeePendingData { managementFee } ... on VaultV2SetPerformanceFeeRecipientPendingData { performanceFeeRecipient } ... on VaultV2SetManagementFeeRecipientPendingData { managementFeeRecipient } ... on VaultV2IncreaseCapPendingData { cap config { id type } } ... on VaultV2SetForceDeallocatePenaltyPendingData { adapterAddress forceDeallocatePenalty } ... on VaultV2AbdicatePendingData { selector functionName } } } } } } ``` ```typescript import { createPublicClient, http, parseAbiItem } from 'viem' import { mainnet } from 'viem/chains' const client = createPublicClient({ chain: mainnet, transport: http(), }) const VAULT_ADDRESS = '0xVAULT_ADDRESS' as `0x${string}` const FROM_BLOCK = 0n // set to vault deployment block // Vault V2 timelocked-action lifecycle events const submitEvent = parseAbiItem('event Submit(bytes4 indexed selector, uint256 validAt, bytes data)') const revokeEvent = parseAbiItem('event Revoke(bytes4 indexed selector)') const acceptEvent = parseAbiItem('event Accept(bytes4 indexed selector)') const [submitLogs, revokeLogs, acceptLogs] = await Promise.all([ client.getLogs({ address: VAULT_ADDRESS, event: submitEvent, fromBlock: FROM_BLOCK }), client.getLogs({ address: VAULT_ADDRESS, event: revokeEvent, fromBlock: FROM_BLOCK }), client.getLogs({ address: VAULT_ADDRESS, event: acceptEvent, fromBlock: FROM_BLOCK }), ]) // simplified: assumes at most one submit/revoke cycle per selector const resolvedSelectors = new Set([ ...revokeLogs.map((l) => l.args.selector), ...acceptLogs.map((l) => l.args.selector), ]) const pendingActions = submitLogs.filter((l) => !resolvedSelectors.has(l.args.selector)) for (const log of pendingActions) { const executableAt = new Date(Number(log.args.validAt) * 1000) console.log(`Pending selector ${log.args.selector} - executable at ${executableAt.toISOString()}`) } ``` ```solidity // Timelock duration for a specific function bytes4 addAdapterSelector = bytes4(keccak256("addAdapter(address,uint256,uint256)")); uint256 duration = IMorphoVaultV2(vault).timelock(addAdapterSelector); // Listen onchain for Submit / Revoke / Accept events: // event Submit(bytes4 indexed selector, uint256 validAt, bytes data) // event Revoke(bytes4 indexed selector) // event Accept(bytes4 indexed selector) // // A config is pending when Submit was emitted and neither Revoke nor Accept // has been emitted for the same selector. // Check if a pending action's timelock has elapsed (can be accepted) // validAt comes from the Submit event args bool canAccept = block.timestamp >= validAt; ``` ### Risk Indicators [#risk-indicators] #### Morpho Vault Warnings [#morpho-vault-warnings] Vault V2 warning `type` can be: * `not_whitelisted` (YELLOW) * `timelock` (RED) * `low_liquidity` (YELLOW) * `deposit_disabled` (RED) * `bad_debt_unrealized` (RED, exposure-gated) * `oracle_unusable` (RED, exposure-gated) * `oracle_price_derivation` (RED, exposure-gated) * `custom` (level configured per entry) Vault V1 uses a different set: * `short_timelock` (RED) * `not_whitelisted` (YELLOW) * `deposit_disabled` (RED) * `low_liquidity` (YELLOW) * `oracle_unusable` (RED, exposure-gated) * `oracle_price_derivation` (RED, exposure-gated) * `custom` (level configured per entry) The default response returns active warnings only. Vault V2 also defines a `GREEN` level for passing checks, but `level_in` replaces the default `[YELLOW, RED]` filter rather than extending it, so `level_in: [GREEN]` hides every active warning. Market warnings do not propagate to vaults: the exposure-gated types above are raised on the vault itself when its exposure to the flagged markets crosses a threshold. See [Morpho Warnings](/get-started/resources/app-ecosystem/#morpho-warnings) for the full type list, criteria, and thresholds. ```graphql title="API" query { vaultV2s(first: 5) { items { name warnings { type level } } } } ``` ## Fee Wrapper [#fee-wrapper] ### FeeWrapper List [#feewrapper-list] ```graphql query VaultV2ByAddress { vaultV2ByAddress( address: "0xd4468EF3745c315949a97090eD27b3F73b9b7C02" chainId: 8453 ) { type address name } } ``` ```graphql query VaultV2s { vaultV2s(skip: 0, first: 20, where: { type_in: [FeeWrapper] }) { items { address name } } } ``` ### FeeWrapper Exposure [#feewrapper-exposure] ```graphql query VaultV2ByAddress { vaultV2ByAddress( address: "0xd4468EF3745c315949a97090eD27b3F73b9b7C02" chainId: 8453 ) { type adapters { items { type address ... on MorphoVaultV2Adapter { address innerVault { address name } } } } historicalState { sharePrice(options: { startTimestamp: 1771015645 endTimestamp: 1771915645 interval: HOUR }) { x y } } } } ``` ```graphql query VaultV2s { vaultV2s(skip: 0, first: 20, where: { type_in: [FeeWrapper] }) { items { type address adapters { items { type address ... on MorphoVaultV2Adapter { address innerVault { address name } } } } historicalState { sharePrice(options: { startTimestamp: 1771015645 endTimestamp: 1771915645 interval: HOUR }) { x y } } } } } ``` ### FeeWrapper Yield [#feewrapper-yield] ```graphql query VaultV2ByAddress { vaultV2ByAddress( address: "0xd4468EF3745c315949a97090eD27b3F73b9b7C02" chainId: 8453 ) { type apy avgNetApyExcludingRewards avgNetApy performanceFee performanceFeeRecipient managementFee managementFeeRecipient rewards { asset { symbol address } supplyApr } } } ``` ```graphql query VaultV2s { vaultV2s(skip: 0, first: 20, where: { type_in: [FeeWrapper] }) { items { type address apy avgNetApyExcludingRewards avgNetApy performanceFee performanceFeeRecipient managementFee managementFeeRecipient rewards { asset { symbol address } supplyApr } } } } ``` ## Position Tracking [#position-tracking] ### User All Vaults Position [#user-all-vaults-position] #### Morpho Vaults V2 [#morpho-vaults-v2-7] ```graphql query GetUserVaultPositions($address: String!, $chainId: Int!) { userByAddress(address: $address, chainId: $chainId) { address chain { id } vaultV2Positions { shares vault { address symbol } } vaultPositions { state { shares } vault { address symbol } } } } ``` ### Vault Depositors [#vault-depositors] #### Morpho Vaults V2 [#morpho-vaults-v2-8] ```graphql title="Unique Vault" query { vaultV2ByAddress( address: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId: 1 ) { positions(first: 10, skip:0) { items { user { address } assets assetsUsd shares } } totalSupply asset { address symbol } } } ``` ### User Earnings [#user-earnings] #### Unique Vault [#unique-vault] ```graphql title="API" query { vaultV2PositionByAddress( userAddress: "USERADDRESS" vaultAddress: "MORPHOVAULTV2ADDRESS" chainId: 1 ) { vault { name address } assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } } ``` #### All Vaults [#all-vaults] ```graphql query { userByAddress(address: "USERADDRESS", chainId: 1) { vaultV2Positions { vault { name address } assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } vaultPositions { vault { name address } state { assets assetsUsd shares pnl pnlUsd roe # time-weighted return since inception (non-annualized) } } } } ``` ### Transaction History [#transaction-history] #### Morpho Vaults V2 [#morpho-vaults-v2-9] ```graphql title="Unique Vault" query { vaultV2transactions( skip: 0 first: 10 orderBy: Time orderDirection: Desc where: { vaultAddress_in: "0x04422053aDDbc9bB2759b248B574e3FCA76Bc145" chainId_in: [1] } ) { items { vault { address } type shares blockNumber timestamp txHash txIndex logIndex } } } ``` ## Historical Data [#historical-data] ### APY Historical State [#apy-historical-state] #### Morpho Vaults v2 [#morpho-vaults-v2-10] Coming soon. --- # Exit an illiquid vault in kind Source: https://docs.morpho.org/developers/earn/tutorials/in-kind-redemption {/* AGENT-GENERATED from in-kind-redemption.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} [In-kind redemption](/curate/concepts/in-kind-redemption/) is the fallback exit for the part of a vault position that cannot be returned through a normal withdrawal because the underlying liquidity is borrowed. It transfers the vault's exposure instead of manufacturing liquidity. `@morpho-org/morpho-sdk@5.5.0` is the first released version that includes the in-kind-redemption API. Use version 5.6.0 or later because its Vault V2 bounded share authorization includes the per-market rounding buffer. VaultExitBundlesV1 is registered on Ethereum, Base, Arbitrum, Optimism, Polygon, World Chain, Unichain, HyperEVM, Katana, Monad, Stable, Tempo, and Robinhood Chain. Custom deployments remain supported; see the [SDK registration example](/developers/sdks/morpho-sdk/vault/#in-kind-redemption). Prefer a normal `withdraw` or `redeem` while the vault can return the underlying asset. Use this flow only for an illiquid remainder, and show the user the assets and market positions they will receive before requesting a signature. ## Before you begin [#before-you-begin] | Vault | User receives | Amount | Required shape | | -------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | -------------------------------------------------------------------------------------- | | Vault V1 | Ordered Morpho Blue supply positions. | Asset-denominated exit amount. | A caller-ordered list of enabled vault markets consumed greedily. | | Vault V2 | Available idle assets first, then ordered Morpho Blue supply positions net of the force-deallocation penalty. | Penalty-inclusive, asset-denominated exit amount. | Exactly one `MorphoMarketV1AdapterV2`; ordered markets are consumed after idle assets. | The SDK does not validate the user's share balance. For Vault V1, size `amount` against the vault contract's `previewRedeem(sharesHeld)`; for Vault V2, keep `amount + BigInt(marketParamsList.length) <= previewRedeem(sharesHeld)` because each market needs a one-asset withdrawal-rounding buffer. `getRequirements()` produces a bounded vault-share authorization: V1 accounts for pending performance-fee shares, while V2 covers the per-market rounding buffer plus separately rounded idle, penalty, and main burns with accrual through the deadline. Simulate the final transaction after authorization because Vault V2 gates and snapshot drift are not fully preflighted. The examples assume a private-key signer for a backend integration. In a frontend, construct the wallet client from the connected provider but keep the same invariant: the account that builds the action signs its requirements and submits the final transaction. ### Step 1: Set up the clients [#step-1-set-up-the-clients] ```typescript import "dotenv/config"; import { createWalletClient, http, parseAbi, publicActions, type Address, } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { mainnet } from "viem/chains"; import { morphoViemExtension } from "@morpho-org/morpho-sdk"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); const client = createWalletClient({ account, chain: mainnet, transport: http(process.env.RPC_URL), }) .extend(publicActions) .extend(morphoViemExtension({ supportSignature: true })); const publicClient = client; const userAddress = account.address; const vaultV1Address = process.env.VAULT_V1_ADDRESS as Address; const vaultV2Address = process.env.VAULT_V2_ADDRESS as Address; const vaultReadAbi = parseAbi([ "function balanceOf(address account) view returns (uint256)", "function previewRedeem(uint256 shares) view returns (uint256)", ]); ``` ### Step 2: Preview a Vault V2 exit [#step-2-preview-a-vault-v2-exit] ```typescript import { previewVaultV2InKindRedeem } from "@morpho-org/morpho-sdk"; const vaultV2 = client.morpho.vaultV2(vaultV2Address, mainnet.id); const [vaultV2Data, latestBlock, sharesHeld] = await Promise.all([ vaultV2.getData(), publicClient.getBlock(), publicClient.readContract({ address: vaultV2Address, abi: vaultReadAbi, functionName: "balanceOf", args: [userAddress], }), ]); const maximumRedeemAssets = await publicClient.readContract({ address: vaultV2Address, abi: vaultReadAbi, functionName: "previewRedeem", args: [sharesHeld], }); // This example passes one market, so reserve the one-asset V2 rounding buffer. const marketRoundingBuffer = 1n; if (maximumRedeemAssets <= marketRoundingBuffer) { throw new Error("Vault V2 position is too small for in-kind redemption"); } const requestedExitAssets = maximumRedeemAssets - marketRoundingBuffer; const [marketChoice] = previewVaultV2InKindRedeem(vaultV2Data, { requestedExitAssets, timestamp: latestBlock.timestamp, }); if (marketChoice == null) { throw new Error("No supported Vault V2 in-kind redemption choice"); } console.log({ marketParams: marketChoice.marketParams, maxExitAssets: marketChoice.maxExitAssets, exitAssets: marketChoice.exitAssets, remainingExitAssets: marketChoice.remainingExitAssets, idleAssets: marketChoice.idleAssets, netAssets: marketChoice.netAssets, feeAssets: marketChoice.feeAssets, }); // This example executes one market choice. Rebuild from fresh state for any // remainingExitAssets after the transaction confirms. ``` ### Step 3: Build and authorize the Vault V2 exit [#step-3-build-and-authorize-the-vault-v2-exit] ```typescript import { isRequirementSignature, type RequirementSignature, } from "@morpho-org/morpho-sdk"; const vaultV2Exit = vaultV2.inKindRedeem({ amount: marketChoice.exitAssets, marketParamsList: [marketChoice.marketParams], vaultData: vaultV2Data, userAddress, }); const vaultV2Signatures: RequirementSignature[] = []; // Requirements authorize only the bounded shares needed for the per-market // rounding buffer and separately rounded idle, penalty, and main burns through deadline. for (const requirement of await vaultV2Exit.getRequirements()) { if (isRequirementSignature(requirement)) { vaultV2Signatures.push(await requirement.sign(client, userAddress)); } else { const hash = await client.sendTransaction(requirement); await publicClient.waitForTransactionReceipt({ hash }); } } const vaultV2Tx = vaultV2Exit.buildTx(vaultV2Signatures); // Simulate after authorization to check gates and post-snapshot state. await publicClient.call({ account: userAddress, to: vaultV2Tx.to, data: vaultV2Tx.data, value: vaultV2Tx.value, }); const vaultV2Hash = await client.sendTransaction(vaultV2Tx); await publicClient.waitForTransactionReceipt({ hash: vaultV2Hash }); console.log("Vault V2 in-kind redemption confirmed:", vaultV2Hash); ``` If the preview reports a non-zero remainder, do not append another preview row to the same call: each row is an alternative single-market choice. Complete the selected partial exit, fetch a new vault snapshot, and preview the remainder again—or build an ordered multi-market list only if your integration independently validates the combined coverage. ## Vault V1 variant [#vault-v1-variant] Vault V1 has no single-market preview helper. Build its ordered market list from the fresh vault snapshot, then let the entity validate coverage before resolving requirements. ### Step 1: Prepare the Vault V1 action [#step-1-prepare-the-vault-v1-action] ```typescript const vaultV1 = client.morpho.vaultV1(vaultV1Address, mainnet.id); const [vaultV1Data, vaultV1SharesHeld] = await Promise.all([ vaultV1.getData(), publicClient.readContract({ address: vaultV1Address, abi: vaultReadAbi, functionName: "balanceOf", args: [userAddress], }), ]); const vaultV1ExitAssets = await publicClient.readContract({ address: vaultV1Address, abi: vaultReadAbi, functionName: "previewRedeem", args: [vaultV1SharesHeld], }); const marketParamsList = [...vaultV1Data.allocations.values()] .filter( ({ config, position }) => config.enabled && position.supplyShares > 0n, ) .map(({ position }) => position.market.params); // The allocations Map is unique by market id. Repeated entries cannot spend // the same vault position twice. const vaultV1Exit = vaultV1.inKindRedeem({ amount: vaultV1ExitAssets, marketParamsList, vaultData: vaultV1Data, userAddress, }); ``` ### Step 2: Authorize, simulate, and submit [#step-2-authorize-simulate-and-submit] ```typescript import { isRequirementSignature, type RequirementSignature, } from "@morpho-org/morpho-sdk"; const vaultV1Signatures: RequirementSignature[] = []; for (const requirement of await vaultV1Exit.getRequirements()) { if (isRequirementSignature(requirement)) { vaultV1Signatures.push(await requirement.sign(client, userAddress)); } else { const hash = await client.sendTransaction(requirement); await publicClient.waitForTransactionReceipt({ hash }); } } const vaultV1Tx = vaultV1Exit.buildTx(vaultV1Signatures); await publicClient.call({ account: userAddress, to: vaultV1Tx.to, data: vaultV1Tx.data, value: vaultV1Tx.value, }); const vaultV1Hash = await client.sendTransaction(vaultV1Tx); await publicClient.waitForTransactionReceipt({ hash: vaultV1Hash }); console.log("Vault V1 in-kind redemption confirmed:", vaultV1Hash); ``` ## Recover from rejected previews or transactions [#recover-from-rejected-previews-or-transactions] | Error | Meaning | Recovery | | --------------------------------------------- | --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | `InKindRedeemRequiresSingleAdapterError` | The Vault V2 snapshot does not contain exactly one adapter. | Use another exit path; in-kind redemption supports one `MorphoMarketV1AdapterV2`. | | `InKindRedeemZeroDeallocationError` | With no idle assets, the penalty-adjusted exit rounds to zero deallocated assets. | Increase the amount or use another exit path. | | `EmptyMarketParamsListError` | Markets are required but the ordered list is empty. | Supply enough current vault markets to cover the non-idle exit. | | `InKindRedeemCoverageError` | The ordered markets cannot cover the requested exit without exceeding a vault position. | Reduce the amount to the error's `maxExitAssets` or rebuild with sufficient current markets. | | `ExpiredDeadlineError` | The deadline has passed at action creation or requirement resolution. | Rebuild the action and resolve its requirements again. | | `InsufficientBlueBalanceForInKindRedeemError` | Morpho Blue cannot fund the required flash loan or largest callback. | Reduce the amount or wait for Blue liquidity. | | `UnknownAddressError` | VaultExitBundlesV1 is not registered for the selected chain. | Register a custom deployment or use a chain with a canonical deployment. | | `VaultIsBlueFeeRecipientError` | A Vault V1 is Morpho Blue's fee recipient, whose accrued fee shares cannot be safely accounted for. | Use another exit path. | | `VaultExitBundlesV1PermitMismatchError` | The supplied permit has the wrong kind, asset, or signature encoding. | Rebuild the action and sign its new vault-exit permit. | RPC or multicall failures surface as `viem.BaseError`; after any quote-time or submission failure, fetch a fresh snapshot and rebuild the action instead of reusing the old transaction. For the complete mechanics, parameter table, and error surface, see the [Morpho SDK vault reference](/developers/sdks/morpho-sdk/vault/#in-kind-redemption). --- # Integrate Rewards for Earn Products Source: https://docs.morpho.org/developers/earn/tutorials/rewards Vault depositors on Morpho can earn rewards beyond base lending yield. Integrating rewards display and claiming is essential for providing competitive APY and maximizing user returns. ## What's Unique for Earn Products [#whats-unique-for-earn-products] When building earn products, understand that vault depositors earn rewards from **two sources**: 1. **Direct Vault Campaigns**: Rewards distributed directly to vault depositors 2. **Forwarded Market Campaigns**: Rewards from underlying markets where the vault allocates liquidity **Critical integration requirement:** ``` Net APY = Native Vault APY + Vault Campaign APR + Forwarded Market Campaign APR - Performance Fee - Management Fee ``` ## Integration Steps [#integration-steps] Follow the centralized rewards tutorials with vault-specific considerations: ### Step 1: Fetch Vault Rewards Data [#step-1-fetch-vault-rewards-data] **Follow:** [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) **Vault-specific notes:** * Query both `state.allRewards` and `state.allocation[].market.state.rewards` * Calculate weighted average for market rewards based on allocation * Use `supplyAssetsUsd` to determine weights * Alternatively, use `netApy` convenience field for simple total APY ### Step 2: Display Rewards in Your UI [#step-2-display-rewards-in-your-ui] **Follow:** [Integrate Rewards Display](/developers/rewards/tutorials/integrate-display) **Vault-specific notes:** * Prominently display Net APY (native + all rewards) * Show breakdown: base yield vs. rewards * List reward tokens with their respective APRs * Optionally show which markets contribute to forwarded rewards ### Step 3: Enable Reward Claiming [#step-3-enable-reward-claiming] **Follow:** [Claim Rewards](/developers/rewards/tutorials/claim-rewards) **Important:** The claiming process is **identical** for all Morpho users (vault depositors, borrowers, market suppliers). Merkl handles all reward claims. ## Complete Integration Example [#complete-integration-example] For a full working reference implementation: **See:** [Complete Rewards Integration Guide](/developers/rewards/guides/complete-integration) This guide includes production-ready code using the [morpho-merkl-recipe](https://github.com/morpho-org/merkl-morpho-recipe). ## Quick Reference [#quick-reference] | Task | Main Tutorial | Vault-Specific Consideration | | ------------------------- | ------------------------------------------------------------------------ | ------------------------------------------- | | **Understanding rewards** | [Reward Campaigns](/developers/rewards/concepts/reward-campaigns) | Understand forwarding mechanism | | **Distribution system** | [Distribution Systems](/developers/rewards/concepts/distribution-system) | Merkl for all users | | **Fetch rewards** | [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) | Query two API locations, manual aggregation | | **Display rewards** | [Integrate Display](/developers/rewards/tutorials/integrate-display) | Show total APY breakdown | | **Claim rewards** | [Claim Rewards](/developers/rewards/tutorials/claim-rewards) | Same process for all users | ## Need Help? [#need-help] * **Conceptual questions**: [Rewards for Lenders Concept](/developers/earn/concepts/rewards) * **Main rewards guide**: [Rewards Integration Guide](/developers/rewards/get-started) * **Technical support**: [help.morpho.org](https://help.morpho.org) --- # Best Practices for Vault Integrators Source: https://docs.morpho.org/developers/earn/vault-ux/best-practices These are best practices, not requirements. Every team's product and users are different, so each item should be adapted to the product context. A few items are flagged because they tend to matter most for end-user trust. ## Product vocabulary [#product-vocabulary] > **Why it matters.** The words set the expectations. Users who hear *staking* expect lock-ups and validator risk. Users who hear *guaranteed* expect a promise no interface can make. Clean vocabulary is the highest-leverage thing any integration can get right, because it flows into every other screen. *Particularly important for user trust.* **Best practices** * Use **Earn** or **DeFi yield** for vault deposits. Integrators should steer away from *staking* or *investment* because those terms carry expectations that do not match how Morpho Vaults work. * When describing yield, **variable** or **indicative** is the framing that tends to hold up best. * Referring to vaults as **Morpho Vaults** or **vault smart contracts** keeps the technical reality intact for users who care to dig in. ## Yield composition [#yield-composition] > **Why it matters.** When rewards get blended into a single headline APY, users expect that number to land in their wallet as the deposit asset. When part of it is a different token, on a different schedule, confusion can arise. Rewards are calculated and displayed as APR, not APY, meaning they reflect the simple annual rate without accounting for the effects of compounding. *Particularly important for user trust.* **Best practices** * When rewards exist, split the displayed yield or return into clear components: * **Base/native APY:** the borrower-paid interest, autocompounding, paid in the deposit asset. * **Rewards APR:** named by token, with schedule where relevant. * Naming the token each component pays in is the detail that does the most work here. A single blended figure, however accurate on paper, sets the wrong expectation. * **Variable** or **indicative** is the framing that holds up across market conditions. *Guaranteed* and *risk-free* do not match the reality of on-chain yield. **Nice to have** * A short note about autocompounding on the product-detail screen is a genuine selling point that gets lost when APY is flattened. * If the rewards program has an end date, showing it prevents the awkward moment when rewards expire. ## Vault transparency [#vault-transparency] > **Why it matters.** Morpho Vault architecture is designed so users know which vault they're in, who curates it, and what collateral or underlying source it is exposed to. *Particularly important for risk surfacing and user trust.* **Best practices** * Showing the **vault name** on the deposit flow, review screen, and post deposit view preserves the information Morpho architecture is designed to expose. Generic labels like "Earn USDC" or wrapped receipt token names tend to obscure it. * Making underlying collateral or adapters reachable in a tap or two from product detail goes a long way. Users do not need every market's full composition up front, but they do benefit from knowing what they're exposed to. Alternatively, linking to Morpho’s page can provide all the advanced vault data. * Adding allocation percentages across underlying markets/adapters, and collateral where relevant gives savvy users a complete picture. * Surfacing instantly withdrawable liquidity can reassure users. * Naming the curator on the product detail or review screen, with their logo where available, treats the curator as the trust signal they are. Links to their website/X account can also be provided. * Showing the vault’s TVL on the product detail or review screen helps users calibrate. * A one line vault thesis ("Blue chip markets curated by X") does a lot of work for low effort. ## Vault benefits messaging [#vault-benefits-messaging] > **Why it matters.** Morpho Vaults offer three key differentiators for end users within integrators’ earn product: no fixed lock-ups, autocompounding yield, and non-custodial architecture. **Best practices** * Communicating the three headline benefits on the product-detail screen tends to work well: * **No fixed lock-ups:** users can attempt withdrawals anytime, with execution subject to available vault or adapter liquidity. * **Native yield auto-compounds:** yield from the vault’s underlying allocations is reflected in the vault receipt token value and paid in the deposit asset. * **Non-custodial:** user assets flow through Morpho Vault smart contracts. ## Educational redirection [#educational-redirection] > **Why it matters.** Users who understand what they're depositing into will deposit more and churn less. A little context in-flow tends to outperform extensive docs buried behind a footer link. **Best practices** * At least one outbound path to Morpho docs or a top-level Earn explainer from the product-detail screen gives curious users somewhere to go. * A clear CTA ("Learn more about Morpho", or contextual equivalent) works better than a buried footer link. ## Activation surfaces [#activation-surfaces] > **Why it matters.** "Eligible assets" prompts and idle-balance nudges are among the highest-intent moments in an integrator app. **Best practices** * When the app surfaces "eligible assets" or an idle-balance prompt, pairing it with a **deposit CTA** inside the component itself keeps the intent alive. * After deposit, showing the user's Morpho powered position alongside the rest of their balance rather than in a separate tab tends to feel more integrated. ## A light self-review [#a-light-self-review] Before launch, a quick pass through this list tends to catch the things that matter most. Treat it as a conversation starter for an internal review, not a checklist to tick off. * Is the language aligned? * Is “Powered by Morpho” visible wherever users interact with Morpho functionality? * Is there a distinct moment where users see the Morpho protocols' disclaimers? * Is the displayed yield or return transparent about where it comes from and what token each part pays in? * Can a user name the vault they're depositing into without leaving the flow? * Is the curator named somewhere a user will actually look? * Are headline benefits visible on product detail? * Is there a path to learn more from the product-detail screen? * Do idle-balance prompts actually go somewhere? --- # UX Requirements Source: https://docs.morpho.org/developers/earn/vault-ux/ux-requirement When building an Earn product on Morpho, you must follow these UX requirements to help users understand what they're interacting with and ensure a transparent experience. ## Brand & attribution [#brand--attribution] > **Why it matters.** The Powered by Morpho badge is the shortest way to communicate to users that your app is the interface and Morpho is the protocol. Integrators who get this framing right tend to have cleaner regulatory conversations and clearer user support. * Surface “Powered by Morpho” on the screens where users actually interact with Morpho functionality, typically the deposit flow, the review step, the post-deposit view, and the product detail screen. * The official `powered-by-morpho` web component or the official static badge both work. If either does not fit a surface, contact the support team so the asset pack can be adapted rather than rebuilding the mark. * Matching the badge theme to the UI surface (light on light, dark on dark) tends to read best. * Somewhere reachable from the badge, users benefit from seeing the short disclaimer. The web component does this via tooltip. **Reference.** [https://brand.morpho.org](https://brand.morpho.org) / [snippet.js](https://brand.morpho.org/snippet.js) ## Morpho disclosures [#morpho-disclosures] > **Why it matters.** Morpho is an immutable, permissionless, and non-custodial protocol and its partners provide interfaces. A distinct moment where users see the Morpho protocols' disclaimers keeps that line legible for users and regulators alike, and protects both sides. Before or upon any end user’s first interaction with the Morpho protocols through an integrator product, present a notice substantially in the following form and ask the user to acknowledge it: “*Accessing the Morpho Protocol through this app is governed by \[Integrator’s] Terms of Use and [Morpho’s Disclaimer](https://morpho.org/disclaimers/). By using it, you acknowledge that you have read and understood these terms and the risks involved*.” This acknowledgment should happen before the user accesses or interacts with the Morpho protocols through the product. **Reference:** [morpho.org/disclaimers](https://morpho.org/disclaimers) --- # Callbacks Source: https://docs.morpho.org/developers/midnight/concepts/callbacks A resting offer normally requires the maker to keep capital available in their wallet, where it cannot simultaneously earn Morpho Blue supply yield. A callback lets an offer source its funds from an external contract at settlement instead, so a maker can keep the capital earning on Morpho Blue until a taker arrives. This page explains what callbacks are, how the Morpho Blue lend callback works onchain, and how Morpho's router recognizes and repurposes that liquidity. ## The idle-capital problem [#the-idle-capital-problem] An offer is a standing commitment. A lender who publishes a buy offer on Midnight is promising to hand over loan tokens the moment a borrower takes it. Without a callback, the loan tokens remain in the maker's wallet until settlement, so the maker must maintain sufficient balance and allowance for the offer to fill. Those funds cannot simultaneously earn Morpho Blue supply yield while backing the offer. Callbacks remove that dead time. Instead of relying on the maker's wallet balance at settlement, the offer names a contract that will produce the funds on demand, at the instant of settlement. Between publishing and being taken, the capital is free to do something productive. For the Blue-buy callback covered below, that "something productive" is supplying the same loan tokens to a Morpho Blue market, where the position can accrue interest at the market's variable supply rate until the offer is taken. That rate changes with market conditions and may fall to zero. ## What a callback is [#what-a-callback-is] Every onchain Midnight `Offer` includes two callback fields: * `callback`: the address of the contract Midnight calls during settlement * `callbackData`: opaque bytes passed through unchanged for that contract to interpret A callback-free offer sets `callback` to the zero address and `callbackData` to `0x`. The REST API serializes `callbackData` as `callback_data`. Callbacks play different roles depending on the offer side. For a buy offer—the lend flow covered on this page—a nonzero `callback` becomes the buyer callback. Midnight calls `onBuy` so the callback can supply the loan tokens instead of the maker's wallet. For a sell offer, the nonzero `callback` becomes the seller callback: Midnight transfers the loan tokens first, then calls `onSell`. A taker can separately provide a taker callback. Each path remains atomic: if a required callback step fails, the whole take reverts. For a buy offer, a lend offer, the hook Midnight calls is `onBuy`. The callback receives how many loan tokens the fill needs (`buyerAssets`), sources exactly that amount, approves Midnight to pull it, and returns a success value. Nothing about this requires the maker's capital to have been idle; it only has to be reachable by the callback when the moment comes. A callback is a general primitive, not a single feature. `blue_buy` (sourcing from a Morpho Blue supply position) is the first reference implementation, and the one the API and router understand today. Other funding sources can be built against the same hook. ## The Blue-buy callback [#the-blue-buy-callback] The reference implementation is `BlueBuyCallback.sol`. It is a small, ownable contract that holds a Morpho Blue supply position and sources a buy offer's funds by withdrawing from it. The maker creates it through the supported chain-specific `BlueBuyCallbackFactory`, funds its Blue position, and points their offers at it. For Morpho's API and router to classify the callback as `blue_buy`, the factory must register the callback for that maker before the offer is posted. An independently deployed contract can implement the same callback hook and settle onchain, but it will not receive this router/API treatment; router validation can report `blue_callback`. Its state is deliberately minimal: ```solidity address public immutable OWNER; // the maker; the only buyer this callback will fund address public immutable MIDNIGHT; // the Midnight contract; the only address allowed to call onBuy address public immutable BLUE; // the Morpho Blue deployment funds are withdrawn from uint256 public nonce; // anti-replay for signed Blue authorizations ``` The settlement hook is the whole story (simplified from source): ```solidity function onBuy( bytes32, Market memory market, uint256 buyerAssets, // exactly what this fill needs uint256, uint256, address buyer, bytes memory data // ABI-encoded Blue MarketParams ) external returns (bytes32) { require(msg.sender == MIDNIGHT, NotMidnight()); // only Midnight may invoke the callback require(buyer == OWNER, NotOwnerBuyer()); // only funds transactions where the owner is the buyer MarketParams memory marketParams = abi.decode(data, (MarketParams)); require(marketParams.loanToken == market.loanToken, InconsistentLoanToken()); // Withdraw the funds from THIS contract's own Blue supply position… IMorpho(BLUE).withdraw(marketParams, buyerAssets, 0, address(this), address(this)); // …and let Midnight pull them to settle the take. ERC20Lib.safeApprove(market.loanToken, MIDNIGHT, buyerAssets); return CALLBACK_SUCCESS; } ``` A few details in that function are the design decisions that make the feature safe and legible: * The Blue position is owned by the callback contract, not the maker's wallet. The `withdraw` call passes `address(this)` as both the position owner and the receiver, so the callback draws on its own supply. This keeps the earning position isolated behind one purpose-built contract with known behavior, which is exactly what lets a stranger's take route through it safely. * Only Midnight can call it, and only for the owner's buys. `NotMidnight` and `NotOwnerBuyer` mean the callback can never be tricked into withdrawing for anyone else's fill. * Loan tokens must match. `InconsistentLoanToken` rejects any `callback_data` whose Blue market pays out a different token than the offer settles in. * It is atomic. The withdraw, the approval, and Midnight's pull all happen inside the taker's single transaction. If the Blue position cannot cover `buyerAssets`, the `withdraw` reverts and the take reverts with it. There is no partial or half-funded state. Three housekeeping functions round it out: `setAuthorization` manages a Blue authorization directly, `setAuthorizationWithSig` performs a signed authorization update (with `nonce` guarding against replay), and `skim` sweeps any stray token balance back to the owner. ## How the router recognizes the liquidity [#how-the-router-recognizes-the-liquidity] A callback would be useless to integrators if the money it can produce were invisible until execution. The point of routing is to know, before anyone commits a transaction, how much of an offer is actually fillable. Callback-backed offers are legible. The offer itself advertises its funding source: the `callback` address and the `callback_data` (the ABI-encoded Blue `MarketParams`) travel with the offer. The Morpho API surfaces this as a typed group — an offer group with `callback.type = "blue_buy"` exposes the callback contract and the Blue market it draws on: ```text GET /v0/midnight/users/{maker}/offer-groups?callback_type=blue_buy // The callback metadata the router reads liquidity against: { "callback": { "type": "blue_buy", "callback_address": "0x...callback", // the BlueBuyCallback contract "market_id": "0x...blueMarket", // the Blue position to inspect "market_params": { "loan_token": "0x...", "collateral_token": "0x...", "...": "..." } } } ``` From there, the router does not take the offer's advertised size at face value. For each callback-backed offer, it computes the callback-funded capacity that can actually be sourced right now as the minimum of three onchain quantities: * remaining group capacity: how much of the offer group is still unconsumed * callback's Blue supply: `supplyAssets` of the callback contract's Blue position * Blue market liquidity: unborrowed liquidity available to withdraw this block This minimum caps that individual callback-backed offer. It is trustless: no promise from the maker, just three reads reconciled into one number. A quote can contain this offer together with other offers and intentional fallback excess, so the quote's `available_assets` is the aggregate capacity of the returned takeable-offer caps and can exceed the requested target. It is not this callback's three-way minimum. When a taker acts on the quote, their SDK or bundler constructs and submits a target-aware transaction from the returned takeable offers, limiting execution to the requested amount despite any fallback excess. Midnight then calls `onBuy`, and funds supplied to Blue—and potentially accruing interest at its variable market rate—are withdrawn and routed into settlement. In the Midnight REST API v0, discover callback groups with `GET /v0/midnight/users/{user-address}/offer-groups`, inspect callback-backed offers with `GET /v0/midnight/books/{market-id}/{side}/takeable-offers` or `GET /v0/midnight/takeable-offers?maker={maker-address}`, and request an execution plan with `GET /v0/midnight/books/{market-id}/{side}/quote`. ## How callback-backed settlement works [#how-callback-backed-settlement-works] A Blue-backed offer follows this settlement flow: 1. the callback only ever withdraws from its own isolated Blue position 2. It will only act for the maker who owns it and only when the Midnight contract is the caller 3. The settlement token is checked against the Blue market 4. The entire funding step lives inside the taker's transaction so a shortfall reverts cleanly rather than settling half a fill The taker experiences a Blue-backed offer through the same take flow as a wallet-funded offer: they submit a take and either get filled at the quoted terms or the transaction reverts. --- # Collateral, Health, Liquidations Source: https://docs.morpho.org/developers/midnight/concepts/collateral-health-liquidations ## Multiple accepted collaterals [#multiple-accepted-collaterals] A Midnight market is comprised of *one* loan token + *one* maturity + a **fixed list of accepted collaterals.** Any borrower in that market can back their single debt with any mix of those listed collaterals (up to 128 per market and up to 16 per position). Each accepted collateral has its own locked LLTV, which determines how much debt that collateral can support. The total amount a borrower can take on is their **maxDebt** (the sum of each posted collateral's value multiplied by its respective LLTV): ```javascript maxDebt = Σ (collateral value × LLTV) ``` Take a **USDC · Dec-2026** market created to accept three collaterals, each with its own locked LLTV: WETH (86%), cbBTC (77%), wstETH (86%). Two different borrowers show up: **Borrower A** posts only 2 WETH (at 3,000 USDC): * collateral value = 6,000 * maxDebt = 6,000 × 0.86 = **5,160 USDC** of borrowing capacity **Borrower B** posts 2 WETH (at 3,000 USDC) *and* 0.1 cbBTC (at 90,000 USDC): * WETH: 6,000 × 0.86 = 5,160 (max debt against WETH collateral) * cbBTC: 9,000 × 0.77 = 6,930 (max debt against cbBTC collateral) * maxDebt = **12,090 USDC** Same market, same maturity, the debt units they create are fully fungible with each other and with every lender's credit, but their maxDebt differs because each borrower chooses a different collateral mix. The set *of accepted* collateral (i.e. WETH, cbBTC, wstETH) is fixed at creation and can never change. Supporting a new collateral means deploying a new market. Within the listed set, though, borrowers compose freely. One lender-side implication worth flagging: borrowers can change their collateral composition at any point (adding, removing, or swapping between accepted collaterals as long as they stay within their maxDebt). This means lending into a multi-collateral market gives you exposure to *all* of its accepted collaterals' risk, not just whatever borrowers happen to be posting today. If a lender only wants WETH-backed exposure, they lend into a market whose accepted set is just WETH. To see each collateral's configuration, use the following endpoint: ```javascript GET /v0/midnight/markets/{market-id} // Response component with "collaterals" field "collaterals": [ { "token": "0xC02a...6Cc2", // WETH "lltv": "860000000000000000", // 86% — high LLTV, considered lower risk "liquidation_cursor": "250000000000000000", "oracle": "0x1234...abcd" // price feed for this collateral }, { "token": "0xcbBT...1234", // cbBTC "lltv": "770000000000000000", // 77% — lower LLTV, considered higher risk "liquidation_cursor": "340000000000000000", "oracle": "0x5678...ef01" } ] ``` ## Position Health [#position-health] Health answers one question: **can a position's collateral still cover what it owes?** A position is healthy as long as its maximum debt capacity is at least its debt: `maxDebt ≥ debt`. After maturity, any outstanding debt makes a position liquidatable regardless of its health. ```javascript // Pre-maturity health check: maxDebt ≥ debt → healthy debt > maxDebt → liquidatable // Post-maturity: // ANY position with debt > 0 is liquidatable, regardless of health ``` Continuing Borrower B from above (`maxDebt` = 12,090), assuming `debt` = 9,000 units: ```javascript maxDebt (12,090) ≥ debt (9,000) → healthy ``` The gap between `maxDebt` and `debt` tells you how much room remains before the position becomes liquidatable. Here, the position can absorb a reduction in `maxDebt` of up to 3,090 units (≈ 25% drop in total collateral value, if both collaterals move together, from 15,000 to \~11,166) before `debt` exceeds `maxDebt`. To fetch a position's state (debt and collateral) for health computation call the following endpoint: ```javascript GET /v0/midnight/markets/{id}/users/{addr}/position // Response { "data": { "chain_id": 1, "market_id": "0xd92de5e7fbb...7614", "user_address": "0xBorrowerB...", "loan_token": "0xa0b8...3c02", // USDC "maturity": "1798761600", "type": "borrow", "credit": "0", "debt": "9000000000", // 9,000 USDC debt units "pending_fee": "0", "loss_factor": "0", "last_loss_factor": "0", "collaterals": [ { "token": "0xC02a...6Cc2", "amount": "2000000000000000000" }, // 2.0 WETH { "token": "0xcbBT...1234", "amount": "10000000000000000" } // 0.1 cbBTC ], "last_indexed_block": "47457420" } } ``` To check whether the position is healthy: 1. For each collateral, read its oracle address from `/v0/midnight/markets/{market-id}` (`collaterals[].oracle`) and query the oracle contract onchain for the price 2. Calculate `collateralValue = amount × oraclePrice` 3. Calculate maxDebt contribution as `collateralValue × lltv` from `/v0/midnight/markets/{market-id}` 4. Calculate the position's `maxDebt` as the sum of all contributions from the previous step 5. Position is healthy if `maxDebt ≥ debt` Health is a property of the whole basket and a liquidator restoring health draws against that combined collateral. Which collateral they seize is an economic choice covered in the Liquidation section that follows. ## Liquidation [#liquidation] ### Maturity [#maturity] A position can be liquidated pre or post maturity. **Pre-maturity (health-factor based):** if a borrower's position becomes unhealthy (their debt exceeds their maxDebt) a liquidator can step in to repay some of the debt and seize collateral of their choosing, in whatever mix they want. The protocol checks that the seize is valued correctly and that they don't exceed the recovery close factor cap. How much can be liquidated depends on position size (see RCF below). **Post-maturity:** once a market's maturity date has passed, any borrower who hasn't fully repaid can be liquidated for the entirety of their debt in a single call. There's no partial liquidation and the position is liquidated, because the grace period is over and lenders need access to their funds. The Recovery Close Factor (RCF) caps how much debt a liquidator can repay in a single liquidation on the **unhealthy path** (pre-maturity). The idea is: if a position is only slightly underwater, the liquidator should only liquidate *enough to restore it to health,* not seize everything. The RCF caps the liquidator's repayment at the amount needed to bring `maxDebt ≥ debt` back into balance, restoring the position to health rather than closing it out entirely. A per-collateral exception applies: if a liquidation would leave less than `rcfThreshold` worth of a given collateral, the liquidator can seize all of that collateral to avoid leaving a residual too small to liquidate profitably. The **post-maturity path** has no RCF cap. After maturity, if a borrower hasn't repaid, the full debt can be liquidated in one call because the grace period is over and lenders need access to their assets. ### Liquidation Incentive Factor (LIF) [#liquidation-incentive-factor-lif] How the liquidator picks is an economic question, not a protocol rule. The dominant driver is that **the liquidation incentive is per-collateral:** each collateral's `max_lif` comes from its own LLTV and cursor γ, via `LIF = 1/(1 − γ(1 − LLTV))`. Lower-LLTV collateral pays a *bigger* bonus. In your two-collateral position (assuming γ = 0.25 on both): * WETH, LLTV 86% → `LIF = 1/(1 − 0.25·0.14) ≈ 1.036` → \~3.6% bonus * cbBTC, LLTV 77% → `LIF = 1/(1 − 0.25·0.23) ≈ 1.061` → \~6.1% bonus The Morpho API returns all the fields you need to calculate the `max_lif` in the market’s definition endpoint: ```javascript GET /v0/midnight/markets/{marketId} // Response component containing lltv and liquidation cursor "collaterals": [ { "token": "0xC02a...6Cc2", "lltv": "860000000000000000", "liquidation_cursor": "250000000000000000", ...}, { "token": "0xcbBT...1234", "lltv": "770000000000000000", "liquidation_cursor": "250000000000000000", ... } ] ``` **LLTV is a risk signal.** A market gets a low LLTV for an asset precisely because that asset is considered riskier to hold and unwind (more volatile, thinner liquidity, more slippage to sell). If a liquidator repays 1,000 USDC of debt: * Take cbBTC → they receive 1,000 × 1.061 = **1,061 USDC** of cbBTC * Take WETH → they receive 1,000 × 1.036 = **1,036 USDC** of WETH So cbBTC is the more profitable grab here, purely on the incentive. But the bonus isn't the only input. A rational liquidator nets it against **execution cost**: how liquid the asset is, slippage to swap it back to the loan token, and gas. If cbBTC were thin and WETH deep, they might take WETH despite the smaller headline bonus, or split across both. The protocol is indifferent; profit-maximization decides. Borrowers can't assume their "preferred" collateral is safe. Liquidators gravitate toward whatever maximizes their return. What’s more, **lenders’ exposure to loss** doesn’t change based on the liquidator’s choice. Because Midnight realizes excess debt as bad debt immediately the haircut to lender credit is computed from the position's shortfall, not from what the liquidator claimed. ### Liquidation queries [#liquidation-queries] Track liquidation events in a market: ```javascript GET /v0/midnight/markets/{id}/transactions ?event_types=partial_liquidation,full_liquidation // Each liquidation event includes: // borrower → whose position was liquidated // collateral → which collateral token was seized // seized_assets → amount of collateral taken // repaid_units → debt units repaid by the liquidator // post_maturity_mode → false = unhealthy path, true = post-maturity path // bad_debt → any unrealized shortfall socialized to lenders // latest_loss_factor → updated market loss factor after this liquidation ``` Check if your position has been liquidated: ```javascript GET /v0/midnight/users/{addr}/transactions ?event_types=partial_liquidation,full_liquidation // If post_maturity_mode is true → liquidation happened after maturity // If post_maturity_mode is false → position went unhealthy before maturity ``` Find all borrowing positions in a market to assess liquidation risk: ```javascript GET /v0/midnight/markets/{marketId}/positions?types=borrow // For each position, compute maxDebt from their collateral: // // 1. Get the market's collateral config (lltv per collateral): // GET /v0/midnight/markets/{marketId} // // 2. For each collateral in the position: // collateralValue = amount × oraclePrice // maxDebtContribution = collateralValue × lltv // // 3. Sum across all collaterals: // maxDebt = Σ maxDebtContribution // // 4. Compare: // maxDebt ≥ debt → healthy, not liquidatable // maxDebt < debt → unhealthy, liquidatable via pre-maturity path // // 5. Post-maturity check (separate from health): // block.timestamp > maturity && debt > 0 → liquidatable via post-maturity path // regardless of whether maxDebt ≥ debt ``` --- # Fees Source: https://docs.morpho.org/developers/midnight/concepts/fees Midnight can charge two protocol-level fees on each market. Fees apply at different moments, to different participants, and are capped independently. * One fee is charged **per trade (settlement fee)**, the moment an offer is taken * The other accrues **continuously over time (continuous fee)** on outstanding credit ## Settlement fee [#settlement-fee] The settlement fee is charged when an offer is taken. Mechanically it **inserts a spread between the buyer's and the seller's settlement price**. The maker's quoted price `P` is the anchor: the maker settles at exactly `P`, and the taker settles on the other side of the spread, a fee `f` away. * Maker posted a buy offer (lending) at price P: the taker is selling units (borrowing) and settles at `P − f` (receiving less) * Maker posted a sell offer (borrowing) at price P: the taker is buying units (lending) and settles at `P + f` (pays more) Because rate is implied from price (`rate = 1/P − 1`), this also means the taker's effective rate is slightly worse than the headline rate on the offer (a lender-taker earns a touch less, a borrower-taker pays a touch more) The **maker is always made whole at their quoted price**. The fee is paid entirely by the **taker.** The protocol keeps the difference. **Settlement fee scales with maturity** A flat per-trade fee would be punishing on short-dated trades and trivial on long-dated ones. A 25 bps fee on a one-week loan is an enormous *annualized* cost, while the same 25 bps on a three-year loan is negligible. Participants reason in annualized rate, so the settlement fee is defined as a function of **time to maturity** (TTM) and calibrated so the *annualized* fee stays bounded regardless of TTM. The fee rate `f(ttm)` is a **piecewise-linear, continuous** function with breakpoints at **0, 1, 7, 30, 90, 180, and 360 days** (constant after maturity and beyond 360 days). Between two breakpoints it interpolates linearly. ## Continuous fee [#continuous-fee] The second fee is unrelated to trading. It accrues **every second on outstanding credit** and is borne by **lenders only:** the holders of credit, and it is capped at **1% annualized**. It doesn't move tokens as it accrues. Instead it accumulates and **materializes when the lender reduces their credit** (selling units, or redeeming at maturity) at which point the accrued amount is netted out of what they receive. On a position, this accrued-but-unrealized amount is exactly the `pending_fee` field (denominated in units). This is why a lender's realizable claim is not simply their `credit`: it is `credit` adjusted for any bad-debt loss factor, then *net of* `pending_fee` → `realizable = credit × (1 − loss_factor) − pending_fee` . **Fee schedule is fixed at entry** When a lender *increases* their credit, the continuous fee that position will pay is crystallized at the value in force at that moment. Later changes to the market's continuous-fee value do **not** retroactively affect existing lending positions. Say a lender enters a 100 credit position. The contract computes: ```javascript pendingFeeIncrease = creditIncrease * continuousFee * TTM ``` The contract stores that on the lender position as a pending fee. That is what the `pending_fee` in the API response represents: it is the remaining non-accrued amount of that continuous fee for this position. ```javascript credit = 100 USDC pendingFee = 10 USDC (computed upfront: credit increase × continuous fee × TTM) face value = 90 USDC (credit - pending fee) ``` As time passes toward maturity, the protocol gradually claims it, that's the accrual. Now assume we are halfway to maturity and 5 USDC of that fee has accrued. The contract applies the **same** deduction to both fields: ```javascript credit = 100 - 5 = 95 pendingFee = 10 - 5 = 5 face value = 95 - 5 = 90 ← unchanged ``` That 5 USDC is collected at the market level as protocol revenue. Now when the user’s position is fetched, we get the following back: ```javascript GET https://api.morpho.dev/v0/midnight/markets /{marketId}/users/{userAddress}/position // Response — snapshot halfway to maturity { "data": { "chain_id": 8453, "market_id": "0xd92de5e7fbb...7614", "user_address": "0xYourWalletAddress", "last_indexed_block": "47457420", "loan_token": "0x8335...2913", // USDC "maturity": "1798761600", "type": "lend", // "lend" | "borrow" | "collateral_only" "credit": "95000000", // 95 USDC: started at 100, protocol has siphoned 5 so far "debt": "0", // borrower side: 0 for a lender "pending_fee": "5000000", // 5 USDC: started at 10, 5 already accrued into continuousFeeCredit "last_loss_factor": "0", "loss_factor": "0", "collaterals": [] // lenders don't post collateral — always empty } } ``` The face value (what the lender is actually owed, i.e. 95 - 5 = 90 USDC) is unchanged since entry. The lender's gross credit drops from 100 → 95 → 90 while the pending fee drops in lockstep, so from the lender's perspective nothing changes (they were always going to get 90). It's the accounting mechanism for recognizing revenue over time without ever changing what the lender is owed. *** --- # Market Mechanics Source: https://docs.morpho.org/developers/midnight/concepts/market-mechanics ## Markets & Units [#markets--units] Every Midnight market is defined at creation by an immutable set of parameters: a **chain ID**, **loan token**, **maturity date**, accepted **collateral assets** (with oracles and LLTVs), and optional **gates**. Once created, these parameters never change. Markets are permissionless, which means anyone can create one. Understanding how a market is defined is essential before reading balances or constructing offers. ### Market immutable configuration [#market-immutable-configuration] ```javascript GET /v0/midnight/markets/{marketId} // Response — these fields are set at creation and never change: { "data": { "chain_id": 1, "market_id": "0xd92de5e7fbb...7614", "loan_token": "0xa0b8...3c02", // USDC "maturity": "1798761600", // Dec 31, 2026 "collaterals": [ { "token": "0xC02a...6Cc2", // wETH "lltv": "860000000000000000", // 0.86 → 86% LLTV "liquidation_cursor": "250000000000000000", "oracle": "0x1234...abcd" }, { "token": "0xcbBT...1234", // cbBTC "lltv": "770000000000000000", // 0.77 → 77% LLTV "liquidation_cursor": "340000000000000000", "oracle": "0x5678...ef01" }, { "token": "0xwstE...5678", // wstETH "lltv": "860000000000000000", // 0.86 → 86% LLTV "liquidation_cursor": "240000000000000000", "oracle": "0x9abc...2345" } ], "enter_gate": "0x0000...0000", // no enter gate "liquidator_gate": "0x0000...0000" // no liquidator gate } } ``` ### Market live state [#market-live-state] To fetch the market’s current state (i.e. settlement fee, total units), use the following endpoint: ```javascript GET /v0/midnight/markets/{marketId}/state // Response — these fields change as the market evolves: { "data": { "chain_id": 1, "market_id": "0xd92de5e7fbb...7614", "total_units": "85000000000", // total units outstanding "tick_granularity": 1, // valid ticks must be multiples of this "settlement_fee_schedule": [ // settlement fee by time-to-maturity { "time_to_maturity_days": 0, "fee_cbp": "0" }, { "time_to_maturity_days": 1, "fee_cbp": "50" }, { "time_to_maturity_days": 7, "fee_cbp": "150" }, { "time_to_maturity_days": 30, "fee_cbp": "400" }, { "time_to_maturity_days": 90, "fee_cbp": "800" }, { "time_to_maturity_days": 180, "fee_cbp": "1200" }, { "time_to_maturity_days": 360, "fee_cbp": "2000" } ], "current_settlement_fee_cbp": "743.5", // interpolated for current TTM "current_settlement_fee_wad": "7435000000000", // same, WAD-scaled "continuous_fee_rate": "158549000", // per-second ongoing fee (WAD) "last_indexed_block": "47457420" } } ``` ## Credit and debt units [#credit-and-debt-units] Within a market, all positions are denominated in **units**. These units have a fixed payoff structure analogous to that of zero-coupon obligations, representing credit for lenders and debt for borrowers. * 1 credit unit = a claim on 1 loan token at maturity (held by lenders) * You buy units → your credit increases → After maturity, withdraw 1:1 * 1 debt unit = an obligation to repay 1 loan token (held by borrowers) * You sell units → your debt increases → Before maturity, repay to close To list a user's position in a market use the following endpoint: ```javascript GET /v0/midnight/users/{addr}/positions?market_ids=0xd92de5e7fbb...7614 // Response — a lend position: { "data": [ { "chain_id": 1, "market_id": "0xd92de5e7fbb...7614", "user_address": "0xYourAddress...", "loan_token": "0xa0b8...3c02", // USDC "maturity": "1798761600", // Dec 31, 2026 "type": "lend", // lend | borrow | collateral_only | null // Position state "credit": "10500000000", // 10,500 units → 10,500 USDC at maturity "debt": "0", // no debt (this is a lend position) "pending_fee": "12000", // continuous fee accrued but not yet deducted from credit "loss_factor": "0", // current market loss factor (bad debt) "last_loss_factor": "0", // loss_factor already applied to this returned position "collaterals": [], // lenders don't post collateral // Performance (only on the list endpoint, not the detail endpoint) "cost_basis": "952380952380952380...", // weighted avg price paid per unit (WAD) "effective_rate_wad": "52000000000000000" // 5.2% annualized } ] } ``` ## How rate emerges from price [#how-rate-emerges-from-price] Units always settle at 1:1 with the loan token at maturity. If you buy a unit for price `P` (where P\<1), you earn the difference. The implied simple rate over the remaining term is `1 / P - 1`. Example: Buy a unit at P = 0.95 with 6 months to maturity: ```text rate = 1 / P − 1 = 1 / 0.95 − 1 = 0.0526 // → 5.26% over the remaining term // → ~10.5% annualized (simple) ``` You pay less than face value today and at maturity, you receive face value. The discount is your return. In **Morpho Blue**, when you lend 1,000 USDC, you receive supply shares. The number of shares depends on the current exchange rate. As borrowers pay interest, the exchange rate grows, i.e. each share becomes worth *more* USDC over time (unless bad debt is socialized). Your share count stays the same, but the value per share floats upward. The rate is baked into the continuously changing exchange rate. All lenders in the same market earn the same rate, and that rate changes every block. In **Morpho Midnight**, when you lend 1,000 USDC, you *buy* credit units at a specific price (say 0.9524 per unit). You get \~1,050 units. Each unit is always worth exactly 1 USDC at maturity. Your return is the gap between what you paid (1,000 USDC) and what you'll receive (1,050 USDC). The rate isn't embedded in the accounting, it was determined by the price you traded at. Two lenders in the same market can have different rates because they entered at different prices. ## Fungibility and secondaries [#fungibility-and-secondaries] Positions within a market are **fully fungible**. Because all positions within a market share the same maturity date, units created from different trades at different times are interchangeable. This fungibility enables **secondary markets**. A lender doesn't need to wait until maturity: a lender can sell their credit units back into the market. A borrower can buy units to close out the borrower’s debt. The same market serves both entries and exits, deepening liquidity for everyone. To track a user’s entries and exits in a market use the following endpoint: ```javascript GET /v0/midnight/users/{addr}/transactions // Response event_types: // lend → bought credit units (entered a lending position) // exit_lend_primary → redeemed credit at/after maturity (1:1 withdrawal) // exit_lend_secondary → sold credit units before maturity (early exit) // supply_collateral → deposited collateral into a market // borrow → took an offer / sold credit units (created debt) // exit_borrow_primary → repaid debt // exit_borrow_secondary → bought units to close debt (early close) // withdraw_collateral → withdrew collateral from a market // partial_liquidation → position partially liquidated (pre-maturity, RCF-capped) // full_liquidation → position fully liquidated // Each event includes: assets, units, maker, taker, buyer, seller, tx_hash ``` You can also choose to filter specific transactions (i.e. lending) and get a subset of event types in the response: ``` GET /v0/midnight/users/{addr}/transactions ?event_types=lend,exit_lend_primary,exit_lend_secondary ``` --- # Mempool & Router Source: https://docs.morpho.org/developers/midnight/concepts/mempool-router ## Mempool [#mempool] Everything so far describes a position you already hold: how its health is measured and what happens if it deteriorates. But a position only exists once a borrower and a lender have traded units. Midnight has no order book matching them: instead, makers broadcast signed offers onchain through the mempool, a minimal contract that simply logs each offer as an event (no storage or mapping is performed by the mempool). The mempool is not a single canonical place. Offers exist entirely offchain so different participants might see different subsets of offers. The competition to find and fill the best offers is the province of **routers.** **Key difference from an order book** On a CLOB (central limit order book), placing an order locks capital and establishes price-time priority. In Midnight, an offer locks nothing and has no protocol-enforced priority. Best-price ordering is instead handled offchain by the Morpho router, explained in the section that follows. ## Router [#router] Because offers float offchain with no canonical queue and no protocol-enforced priority, a taker looking for the best rate faces a genuine **search problem**. The process of discovering, evaluating, and selecting which offers to take is called **routing**. Because routing is permissionless, anyone can run one. **Morpho operates its own router**, which aggregates offers and surfaces executable offers so integrators don't have to build discovery infrastructure themselves. ### Validating offers [#validating-offers] The Morpho router only indexes offers that pass its rules. Each rule constrains one dimension of an offer. If any rule is violated, Morpho's router ignores the offer. | Rule | What it checks | Issue names | | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | | **Payload version** | Must use the current version byte. | `payload_version` | | **Payload framing** | Structure must follow: version ‖ uint32(gzipLen) ‖ gzip(ABI-encoded offers) ‖ optional suffix. Gzip length must honestly delimit the compressed ABI payload. Suffix must not exceed allowed size. | `payload_frame`, `payload_gzip_length`, `payload_suffix_too_large`, `payload_decompression`, `payload_abi_decode` | | **Payload content** | Must contain at least one non-empty offer. No duplicate offer hashes. Item count must not exceed the active tree limit. Padding offers for Merkle trees are fine. | `empty_payload`, `duplicate_offer_hash`, `max_offers_per_tree` | | **Chain** | Every request is chain-scoped. The chain must be configured by the router. | `unsupported_chain` | | **Maturity** | The offer's market must use a supported maturity bucket for the target chain. | `maturity` | | **Loan token** | The market's loan token must be supported on the target chain. Token support is dynamic, do not rely on static lists. | `loan_token` | | **Collateral tokens** | Every collateral must be supported on the target chain. Total collateral count must not exceed the active maximum. | `collateral_token`, `max_collaterals` | | **Oracle address** | Each collateral must include a non-zero oracle address. Use the oracle expected by the market. The router does not call price() during validation. This is a format check, not a live price read. A payload can pass validation and still fail to become takeable if the router cannot index a usable price. | `oracle` | | **LLTV** | Each collateral's LLTV must be one of the modes the router supports for safe routing. | `collateral_lltv` | | **Max LIF** | The liquidation incentive factor derived from LLTV and cursor must be within the accepted range. | `max_lif` | | **Size expression** | Each offer must set exactly one of max\_units or max\_assets. Do not set both. Do not leave both at zero. | `amount_missing`, `amount_conflict` | | **Minimum size** | When active for a chain and loan token, offers below a USD-denominated floor are rejected. Per-token floors are dynamic, always validate rather than hardcoding. | `min_offer_assets_usd` | | **Tick range** | The offer's tick must fall within the router's supported range. | `min_tick`, `max_tick` | | **Tick spacing** | The tick must align to the configured spacing for the market. | `tick_spacing` | | **Minimum duration** | The offer's active window (start → expiry) must be long enough to be useful. Very short-lived offers are rejected. | `min_duration` | | **Group time overlap** | If a group contains multiple offers for the same market, their active windows must not overlap. One group can represent alternatives over time, not simultaneous duplicates. | `group_consistency` | | **Callback policy** | Empty-callback buy/sell support is policy-controlled. Recognized Blue callback offers are checked separately. Validate rather than assuming support won't change. | `non_empty_callback`, `buy_empty_callback`, `sell_empty_callback`, `blue_callback` | | **Ratifier support** | Every offer declares a ratifier contract. The ratifier must be accepted for the target chain. | `ratifier` | | **Batch consistency** | All offers in one batch must share the same maker and the same ratifier. The router checks the declared ratifier address but does not authenticate ratifierData at validation time. Signatures and proofs are checked later by the indexer or onchain. | `mixed_maker`, `mixed_ratifier` | | **Group identity** | Groups are content-addressed. The group ID must match the hash of its offers after zeroing offer.group and sorting the offer hashes. | `group_identity` | | **Group field consistency** | Offers sharing a group must agree on: loan token, max\_units, max\_assets, side (buy/sell), callback address, and callback data. Same-market offers within a group must not have overlapping active windows. | `group_consistency` | The `/mempool/validate` endpoint is a pre-flight check: "will the router index this offer?" If validation returns issues, the offer can still exist in the mempool, but the router ignores it. ```javascript // Check if the router will index your offers POST /v0/midnight/mempool/validate { "chain_id": 1, "payload": "0x01..." } // Response (accepted payload) { "data": { "issues": [] } } // Response (rejected-by-policy payload) { "data": { "issues": [ { "rule": "loan_token" }, { "rule": "maturity" }, { "rule": "tick_spacing" } ] } } ``` For example, to confirm ratifier support, the validate endpoint receives both the `Offer` struct and the `ratifierData` (e.g., the ECDSA signature or Merkle proof) for a given offer. The verification is two-part: * **Allowlist check**: The router checks that `offer.ratifier` is a ratifier contract it knows how to validate. * **Signature check**: The router replicates the ratifier's logic offchain (for an ECDSA ratifier, it runs `ecrecover(hash(offer), signature)` ) and confirms the recovered address matches `offer.maker`. It proves the maker actually signed this specific offer, not just that someone submitted it with a known ratifier address. Without this, the book would fill with forged offers that look valid but revert onchain. ### The order book [#the-order-book] The router aggregates all signed & active offers from the mempool that pass its rules in the form of a book (a bid/ask market view). It groups them by tick and sums the units and assets at each tick into a price level (price level = one tick). **Why "asks" and "bids"?** Make/take describes what a *participant does.* "I make an offer," "someone takes it." Ask/bid describes the *aggregated state of the market:* "there are asks at this price, bids at that." Both sides of the book are made up of makers' offers; the taker is the person reading the book and deciding what to hit. An **ask** is a sell-unit offer (the maker wants to borrow). A **bid** is a buy-unit offer (the maker wants to lend). If you want to lend, you *take asks*. If you want to borrow, you *take bids*. To fetch a single book's state run the following query: ```javascript GET /v0/midnight/books?chain_ids=8453&limit=1 // Response { "cursor": null, "data": [ { "market_id": "0x52b0f65ac6906080cb2100a2ffbef8f81c6c40ec65ebca6b5fc6ad4412ad77cf", "id": "0x52b0f65ac6906080cb2100a2ffbef8f81c6c40ec65ebca6b5fc6ad4412ad77cf", "chain_id": 8453, "midnight": "0x2f7a3aa739ba5792ce1b4ea046117f2c0095bca6", "loan_token": "0x4200000000000000000000000000000000000006", "collaterals": [ { "token": "0xcbb7...33bf", "lltv": "860000000000000000", "liquidation_cursor": "250000000000000000", "oracle": "0x10b95702a0ce895972c91e432c4f7e19811d320e" } ], "maturity": 1783090800, "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000", "asks": [ { "tick": 4736, "price": "998890800000000000", "units": "666666666666666", "assets": "665927200000000", "count": 2 }, { "tick": 4740, "price": "998912700000000000", "units": "1333333333333334", "assets": "1331883600000002", "count": 2 }, { "tick": 4744, "price": "998934100000000000", "units": "2000000000000000", "assets": "1997868200000000", "count": 2 } ], "bids": [ { "tick": 4732, "price": "998868400000000000", "units": "667421921312824", "assets": "666666666666666", "count": 2 }, { "tick": 4728, "price": "998845700000000000", "units": "1334874178597690", "assets": "1333333333333334", "count": 2 }, { "tick": 4724, "price": "998822400000000000", "units": "2002357976753426", "assets": "2000000000000000", "count": 2 } ] } ] } ``` To fetch a book with a specific depth (max price levels per side), run the following query: ```javascript GET /v0/midnight/books/{market-id}?depth=50 ``` To only fetch the book's ask side, run the following query: ```javascript GET /v0/midnight/books/{market-id}/asks // Response { "data": [ { "tick": 4736, "price": "998890800000000000", "units": "666666666666666", "assets": "665927200000000", "count": 2 }, { "tick": 4740, "price": "998912700000000000", "units": "1333333333333334", "assets": "1331883600000002", "count": 2 }, { "tick": 4744, "price": "998934100000000000", "units": "2000000000000000", "assets": "1997868200000000", "count": 2 } ] } ``` ### Takeable offers [#takeable-offers] The book endpoint returns only aggregated summaries. A price level tells you "10,000 units are available at price 0.9524". To execute, you need the full `Offer` struct plus the `ratifierData`. That's what a **takeable offer** is: an individual offer that is currently executable, packaged with everything needed to call the Midnight contract onchain. The `/quote` endpoint returns these individual offers complete with `Offer` structs and `ratifierData` ready for onchain submission. You tell it how much you want to trade, a market, a side, a target size, and optional slippage and it searches the mempool, ranks offers by price, and returns a bundle-ready execution plan. A note on fallback excess returned by the quote: 1. When you request a quote for 10,000 units, the router returns more than 10,000 (say 15,000 across four offers, sorted best price first, then earliest first within the same tick). This excess is intentional. 2. Between the moment you receive the quote and the moment your transaction lands onchain, other takers may have partially consumed some of those same offers. 3. If offer A originally had 5,000 units but someone else took 3,800 of them in the meantime, only 1,200 remain when your transaction executes. 4. Your bundler skips what it can't fill from A and moves to offers B and C to reach your 10,000 target. The fallback offers absorb the impact of this concurrency. 5. You may get a slightly worse average price (since later offers sit at deeper ticks), but you still fill your slippage target / price tolerance. For example, if you want to lend 10,000 USDC and take asks you run the following query: ```javascript GET /v0/midnight/books/{market-id}/asks/quote ?assets=10000000000 &slippage=0.5 // Response { "data": { "average_best_price": "998890800000000000", "average_worst_price": "1003885254000000000", "available_assets": "14983800", "available_units": "15000000", "takeable_offers": [ { "market_id": "0xa249f0b3ffe8891c96ec93fd9c7d00e479da04e4ffe2f2502ea0e5db8eabc75d", "units": "833333", "offer": { "buy": false, "maker": "0x6ba008e3f6ec55dc6412e459ac67949c6d1620c5", "max_units": "833333", "max_assets": "0", "tick": 4736, "start": 1782484425, "expiry": 1783090799, "group": "0x6c544af73868898482d1802005d87e7a4d1100d4e1a6e099a978f6bd2e6cba17", "ratifier": "0x1db7b555ce19e85e625428b0e1047da3198211ea" }, "ratifier_data": "0x0000000000000000000000000000000000000000..." }, // more offers {...} ] } } ``` --- # Multi-Market Offers Source: https://docs.morpho.org/developers/midnight/concepts/multi-market-offers ## Liquidity fragmentation [#liquidity-fragmentation] Isolated markets fragment liquidity. A single fixed-rate market is one loan token, one maturity, and one collateral set, so a lender willing to lend "USDC around 5%" faces a dozen separate books: USDC against wETH, against cbBTC, against wstETH, at the December maturity, the March maturity, and so on. Their willingness to lend is one pool of capital, but the venue splits it into many. The naïve options are bad and result in less liquidity in the system. 1. **Provision capital to every market:** most of it sits idle as unfilled depth (a real opportunity cost that makers respond to by quoting less) 2. **Pick one/fewer markets:** with this you end up missing every fill that lands elsewhere. 3. **Quote wider spreads:** compensates for the cost of capital when they do get filled The book is thinner than the lender's actual appetite, and thin books are exactly what stop new collateral configurations and maturities from bootstrapping. ## Multi-market offers [#multi-market-offers] A maker signs several offers across different markets and assigns them to a common **consumption group** that shares a single **fill budget**. The budget represents the total takeable liquidity the maker is willing to commit across those offers. Each offer is individually takeable up to the full *remaining* budget. The instant any one offer is filled, the shared budget drops. When the budget reaches zero, no offer in the group can be filled, regardless of how many are still signed and floating. In other words, a maker can quote across a new 9-month maturity *without locking a single dollar*. They add depth for free, borrowers see a usable book, fills happen, and the market bootstraps. The barrier to launching a new collateral configuration or maturity drops from "convince someone to lock real capital in a market with unproven demand" to "convince someone to sign an offer." A multi-market offer lets a maker post **one balance across many books simultaneously**. The same dollar is take-able in every linked market, but can only be filled once. Exposure is bounded by the shared balance, not by the sum of all the offers' sizes. Steps: 1. Sign the basket: One signature over a Merkle root of all offers in the group 2. Distribute: Picked up by Morpho’s router 3. A taker fills one offer: Settlement updates the group's shared consumed; the taker supplies the Merkle proof 4. Update balance: Remaining budget = `budget - consumed`. Every linked offer is now fillable only up to that. ### Validate before publishing [#validate-before-publishing] All offers in one batch must share the same maker and ratifier. The router [validates](/developers/midnight/concepts/mempool-router/#validating-offers) the full payload including group consistency. If “group\_identity” or “group\_consistency” appears in issues, the group ID doesn't match the content hash of the offers, or the offers disagree on loan token / side / max expression. ```javascript POST /v0/midnight/mempool/validate { "chain_id": 1, "payload": "0x01..." // version + gzip(ABI-encoded (Offer, ratifierData)[]) } ``` ### Monitoring the group [#monitoring-the-group] After publishing, track the group's fill progress across all markets. Each offer shows max\_assets: "21000000" (the original offer size), but consumed: "10000000" at the group level means only 11 USDC is actually takeable across all three. The budget lives on the group, not the individual offer. ```javascript GET /v0/midnight/users/{makerAddress}/offer-groups?groups={groupId} // Response — one group with its offers across three markets: { "data": [ { "id": "0xGroupId...", "chain_id": 1, "max_assets": "21000000", // 21 USDC budget for the whole group "max_units": "0", // budget expressed in assets, not units "consumed": "10000000", // 10 USDC filled so far (across all markets) // remaining budget: 21 − 10 = 11 USDC "created_at": 1719000000, "expiry": 1719604800, // latest expiry across all offers in the group "offers": [ { "market_id": "0xCbBTC-Dec26...", // cbBTC market — this is where the 10 was filled "buy": true, // buy offer = lend side "tick": -42, "max_assets": "21000000", // each offer reads "up to 21" individually "maker": "0xMaker...", "start": 1719000000, "expiry": 1719604800 }, { "market_id": "0xWETH-Dec26...", // wETH market — still live, up to 11 remaining "buy": true, "tick": -43, "max_assets": "21000000", "maker": "0xMaker...", "start": 1719000000, "expiry": 1719604800 }, { "market_id": "0xWstETH-Dec26...", // wstETH market — still live, up to 11 remaining "buy": true, "tick": -41, "max_assets": "21000000", "maker": "0xMaker...", "start": 1719000000, "expiry": 1719604800 } ] } ] } ``` There is no active "rebalancing service" doing this. Every offer in the group reads the same onchain `consumed` counter, so a fill in one market is simply visible to all the others as less budget left. The offers don't shrink, the shared budget they all draw on does. **Example** A maker posts a **21 USDC** budget across three markets (one consumption group). Each offer reads "take-able up to 21." A taker fills **10 USDC** on the cbBTC market. The shared budget is now **11,** so the wETH and wstETH offers immediately read "take-able up to 11," not 21. The books stay firm: the full remaining balance is take-able on any single market until it's gone. Without the shared budget, quoting 21 USDC on three markets would mean committing 63 USDC of capital (or risk being over-filled). With one consumption group the maker commits 21 and is present in all three books. Signing a separate offer for every market would not scale (a maker quoting twenty markets would sign twenty times). Instead, a ratifier can ratify a **Merkle root of an offer set**: the maker commits to the entire basket with a single signature or interaction, and each individual offer is taken later by presenting its **Merkle proof** as part of the ratifier data. So the two mechanics compose: 1. The **Merkle root** makes a large basket cheap to authorize. 2. The **consumption group** makes that basket safe to leave outstanding by capping total fills at the shared budget. ### Fetch takeable offers [#fetch-takeable-offers] A taker (or your frontend) can fetch all active offers from a maker, filtered by group. Offers with units: "0" are in the group but currently not executable (budget exhausted, expired, or not yet started). ```javascript GET /v0/midnight/takeable-offers?maker={makerAddress}&groups={groupId} // Response — individual executable offers with full Offer struct + ratifier data: { "data": [ { "market_id": "0xWETH-Dec26...", "units": "11550000", // max takeable units (derived from 11 USDC remaining) "ratifier_data": "0xMerkleProof...", // Merkle proof for this specific offer "offer": { "buy": true, "tick": -43, "maker": "0xMaker...", "ratifier": "0xMerkleRatifier...", "group": "0xGroupId...", "maxAssets": "21000000", // ... full offer struct } }, { "market_id": "0xWstETH-Dec26...", "units": "11470000", "ratifier_data": "0xMerkleProof...", "offer": { "buy": true, "tick": -41, ... } } // cbBTC offer might show units: "0" if its remaining budget is fully consumed ] } ``` One signature, many markets, bounded exposure. --- # Tick Structure Source: https://docs.morpho.org/developers/midnight/concepts/tick-structure ## Discrete price grid [#discrete-price-grid] Midnight enforces a **discrete price grid** for offers. Offers can only be placed at prices corresponding to valid ticks. This prevents economically meaningless undercutting (offering at a price 1 wei better) and creates natural price levels around which liquidity concentrates. The market state endpoint gives you `tick_granularity` , the practical constraint a maker needs before constructing an offer: ```javascript // Check what tick values are valid for this market GET /v0/midnight/markets/{marketId}/state // In the response: "tick_granularity": 1 ``` Valid ticks must be multiples of this value. * `tick_granularity`: 1 means every integer tick is valid * `tick_granularity`: 2 would mean only even ticks (42, 40, 38...) ## Constant relative change [#constant-relative-change] The tick grid is designed so that consecutive ticks correspond to a **constant relative change in implied return**, not a constant absolute price step. This matters because the end goal of the activity is a return, and a fixed price increment implies different rate increments depending on how far prices are from par. ```javascript // The unquantized price at tick n, where δ is the target relative increment in implied return P(n) = 1 / (1 + (1 + δ)(N/2 − n)) // Consecutive ticks satisfy: return(n−1) = (1 + δ) × return(n) ``` This means the relative change in implied rate is constant across the grid (each step is the same percentage bigger than the last, not the same absolute amount). `δ` is fixed at the protocol layer (`δ` = 0.02, i.e. 2%). All markets share the same tick spacing and `δ` is not configurable per market. In order to see the actual tick-to-price mapping where liquidity exists, use the following query: ```javascript GET /v0/midnight/books/{marketId}/asks?depth=4 // Each price level shows the tick and its corresponding price: { "data": [ { "tick": 42, "price": "952380952380952320", "units": "10000000000", "count": 3 }, { "tick": 43, "price": "951474785918173312", "units": "18500000000", "count": 5 }, { "tick": 44, "price": "950552270869375104", "units": "7200000000", "count": 2 }, { "tick": 45, "price": "949613146596339456", "units": "4100000000", "count": 1 } ] } // tick 42: price ≈ 0.9524 → rate = 5.000% // tick 43: price ≈ 0.9515 → rate = 5.100% (5.100 / 5.000 = 1.02 exactly) // tick 44: price ≈ 0.9506 → rate = 5.202% (5.202 / 5.100 = 1.02 exactly) // tick 45: price ≈ 0.9496 → rate = 5.306% (5.306 / 5.202 = 1.02 exactly) // Every consecutive pair: rate(n-1) / rate(n) = 1.02 = 1 + δ ``` Near par (P ≈ 1), where rate is close to zero, the rate-based spacing becomes impractically fine, so prices are quantized to a granularity of `ε = 1×10⁻⁶`. This lets the same immutable grid handle both far-from-par and near-par quoting. The protocol is designed so that tick spacing could be refined per market in the future (only made finer, never coarser). A market would start with wide ticks (δ=2%) and graduate to tighter quoting (δ=1% or δ=0.5%) as liquidity deepens. The definition of ticks ensures that enabling finer spacing would never invalidate existing offers. This capability is not yet active: all markets currently share a single fixed grid at δ=0.02. --- # Midnight: Developer Resources Source: https://docs.morpho.org/developers/midnight/resources/all This page is your central hub for all the tools, links, and data sources you need to build on Morpho Midnight. ## Data & Contracts [#data--contracts] These resources are for interacting directly with the blockchain. * **Contract Specifications**: For detailed function signatures, events, and errors, refer to the canonical contract specs: * [**Midnight Core Contract**](https://github.com/morpho-org/midnight/blob/main/src/Midnight.sol): immutable fixed-rate lending primitive via isolated permissionless markets * [**Midnight Bundles**](https://github.com/morpho-org/bundles/blob/main/src/midnight/MidnightBundlesV1.sol): atomic multi-step transactions (e.g., supply collateral + take in one call) * **GitHub Repositories**: * [**Midnight**](https://github.com/morpho-org/midnight): The core protocol contracts (Midnight.sol, interfaces, libraries). ## Integration Methods [#integration-methods] These tools provide easier access to onchain data for frontends, backends, and analytics. * **Midnight REST API**: Indexed onchain state (markets, positions, transactions, offer groups, and performance data). * **API Reference**: [Morpho Midnight API examples](/developers/api/morpho-midnight/) * **Base URL**: `https://api.morpho.org/v0/midnight/` * **Tutorials:** * [Lend at a fixed rate](/developers/midnight/tutorials/lend-at-a-fixed-rate/) * [Borrow at a fixed rate](/developers/midnight/tutorials/borrow-at-a-fixed-rate/) * [Exit borrow at maturity](/developers/midnight/tutorials/exit-borrow-by-repaying-in-full-at-maturity/) * [**Morpho SDK — Midnight**](/developers/sdks/morpho-sdk/midnight/): ready-to-send Midnight taker, maker, and position-maintenance transactions, plus the low-level toolkit for constructing offers, computing ticks, and encoding payloads. * [**Midnight Quoter bot**](https://github.com/morpho-org/morpho-bots/tree/main/bots/quoter-bot): self-hosted reference bot for makers. It validates setup, bootstraps lending positions, and maintains two-sided rate ladders around a reference rate. Start in read-only mode before enabling signing. The CLI is available as [`@morpho-org/quoter`](https://www.npmjs.com/package/@morpho-org/quoter). ## Other [#other] * **Validation Rules Reference**: Full table of Router [mempool rules](/developers/midnight/concepts/mempool-router/#validating-offers) (what the Router checks before indexing an offer) --- # Borrow at a fixed rate Source: https://docs.morpho.org/developers/midnight/tutorials/borrow-at-a-fixed-rate {/* AGENT-GENERATED from borrow-at-a-fixed-rate.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} ## **Wallet setup** [#wallet-setup] Define the wallet client and the borrower's address. This is the account that will sign transactions and receive units at maturity. ```typescript import { createWalletClient, http, parseUnits, zeroAddress, type Address, } from "viem"; import { base } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; // The borrower (taker of bids), i.e. your wallet address const taker: Address = "0xYourWalletAddress"; // Create a wallet client connected to Base // This example uses a simple EOA with a private key for brevity // You can substitute in any wallet solution that fits your setup const walletClient = createWalletClient({ account: privateKeyToAccount("0xYourPrivateKey"), chain: base, transport: http(), }); ``` ## **Browse markets** [#browse-markets] Fetch the list of books filtered by chain and loan token. Each book represents one market with its top ask/bid price levels: ```javascript GET https://api.morpho.org/v0/midnight/books ?chain_ids=8453 // Base &loan_tokens=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 // USDC on Base &sort=maturity // Response (each book is one market with its top ask/bid levels) { "cursor": "eyJzb3J0Ijpb...", // null when there is no next page "data": [ { "market_id": "0xd92de5e7fbb...7614", // use this ID in the next step "chain_id": 8453, "midnight": "0x2F7a3AA739ba5792Ce1b4eA046117f2C0095BCA6", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "collaterals": [ { "token": "0x4200000000000000000000000000000000000006", // WETH on Base "lltv": "770000000000000000", // 77% LTV "liquidation_cursor": "250000000000000000", "oracle": "0xD09048c8B568Dbf5f189302beA26c9edABFC4858" } ], "maturity": 1798761600, // January 1, 2027 "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000", "asks": [ { "tick": 4990, "price": "957900000000000000", "units": "5000000000", "assets": "4789500000", "count": 1 } ], // lending side; ignore for borrowing // ┌──────────────────────────────────────────────────────────────┐ // │ FOR BORROWING → read the BIDS │ // │ Bids = lenders buying units = offering to lend you money │ // │ You (the borrower) take these bids = sell your units │ // │ The bid price is what you receive per unit → your cost │ // └──────────────────────────────────────────────────────────────┘ "bids": [ { "tick": 4988, "price": "956937000000000000", // ≈ 0.9569 per unit "units": "8000000000", "assets": "7655497000", // ≈ 7,655 USDC on the bid "count": 2 }, { "tick": 4976, "price": "951000000000000000", // ≈ 0.9510 (worse for borrower) "units": "12000000000", "assets": "11412000000", "count": 4 } ] } ] } ``` ## **Select market & estimate borrow rate** [#select-market--estimate-borrow-rate] The best bid is the highest price. Higher means you receive more per unit sold, so lower borrowing cost. ```typescript // Select book data const book = response.data[0]; // Obtain best bid price (highest = cheapest to borrow) const bestBidPrice = Number(book.bids[0].price) / 1e18; // ≈ 0.9569 // Calculate time to maturity (ttm) in seconds const ttm = book.maturity - Date.now() / 1000; // Calculate fixed borrow APR const borrowAPR = (1 / bestBidPrice - 1) * (365 * 86400) / ttm; console.log(`Borrow APR: ${(borrowAPR * 100).toFixed(2)}%`); // If the rate looks good, save the market ID for the next step const marketId = book.market_id; // "0xd92de5e7fbb...7614" ``` ## **Get a quote for your offer** [#get-a-quote-for-your-offer] ### API [#api] #### Step 1: Get a quote [#step-1-get-a-quote] Specify the amount of USDC you want to borrow from a market with slippage. Morpho's router returns executable takeable offers (quotes against the bids) with fallback liquidity. The total may exceed your target so the bundle can skip stale offers and still fill. ```typescript GET https://api.morpho.org/v0/midnight/books/{marketId}/bids/quote ?assets=10000000000 // 10,000 USDC (6 decimals) &slippage=0.5 // 0.5% slippage guard &limit=10000 // max offers walked/returned; default and maximum 10,000 // Response - the Router's execution plan: { "data": { "average_best_price": "956937000000000000", // realized target fill using onchain per-offer rounding, rounded against the taker "average_worst_price": "952200000000000000", // conservative adverse-placement bound across returned caps; not the slippage-derived guard "available_assets": "14000000000", // max assets in this returned limit window "available_units": "14630000000", // max units in this returned limit window "takeable_offers": [ { "market_id": "0x56b822d7d8be6bded8e1782277989fe5dc4e5b0a4f7f9d349482bc933bd5436c", "units": "8000000000", // maximum this offer may contribute, not an exact allocation "offer": { "market": { "chain_id": 8453, "midnight": "0x2F7a3AA739ba5792Ce1b4eA046117f2C0095BCA6", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "collaterals": [ { "token": "0x4200000000000000000000000000000000000006", "lltv": "860000000000000000", "liquidation_cursor": "300000000000000000", "oracle": "0xD09048c8B568Dbf5f189302beA26c9edABFC4858" } ], "maturity": 1786147200, "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000" }, "buy": true, // buy offer (bid): the maker is lending "maker": "0x7b093658BE7f90B63D7c359e8f408e503c2D9401", "max_units": "8000000000", "start": 1761922790, "expiry": 1786147200, "tick": 4988, "group": "0x000000000000000000000000000000000000000000000000000000000008b8f4", "callback": "0x0000000000000000000000000000000000000000", "callback_data": "0x", "receiver_if_maker_is_seller": "0x7b093658BE7f90B63D7c359e8f408e503c2D9401", "ratifier": "0x0000000000000000000000000000000000000002", "reduce_only": false, "max_assets": "0", "continuous_fee_cap": "115792089237316195423570985008687907853269984665640564039457584007913129639935" }, "ratifier_data": "0x7c1e...d402" } // Additional complete offers follow in best-price order as fallback excess. ] } } // Interpreting the quote const quote = response.data; const avgPrice = Number(quote.average_best_price) / 1e18; // 0.9569 const borrowAPR = (1 / avgPrice - 1) * (365 * 86400) / ttm; // To borrow 10,000 USDC at price ≈ 0.9569 you sell ≈ 10,450 units // At maturity you repay 10,450 USDC → net cost ≈ 450 USDC const unitsToSell = Math.ceil(10000e6 / avgPrice); ``` #### Step 2: Convert takeable offers to ABI-encoded data [#step-2-convert-takeable-offers-to-abi-encoded-data] The quote API returns snake\_case JSON, whereas the onchain ABI expects camelCase tuples. The `apiOfferToAbi` helper converts the API offer to an ABI struct. ```typescript function apiOfferToAbi(apiOffer: any) { return { market: { chainId: BigInt(apiOffer.market.chain_id), midnight: apiOffer.market.midnight, loanToken: apiOffer.market.loan_token, collateralParams: apiOffer.market.collaterals.map((c: any) => ({ token: c.token, lltv: BigInt(c.lltv), liquidationCursor: BigInt(c.liquidation_cursor), oracle: c.oracle, })), maturity: BigInt(apiOffer.market.maturity), rcfThreshold: BigInt(apiOffer.market.rcf_threshold), enterGate: apiOffer.market.enter_gate, liquidatorGate: apiOffer.market.liquidator_gate, }, buy: apiOffer.buy, maker: apiOffer.maker, maxUnits: BigInt(apiOffer.max_units), maxAssets: BigInt(apiOffer.max_assets), start: BigInt(apiOffer.start), expiry: BigInt(apiOffer.expiry), tick: BigInt(apiOffer.tick), group: apiOffer.group, callback: apiOffer.callback, callbackData: apiOffer.callback_data, receiverIfMakerIsSeller: apiOffer.receiver_if_maker_is_seller, ratifier: apiOffer.ratifier, reduceOnly: apiOffer.reduce_only, continuousFeeCap: BigInt(apiOffer.continuous_fee_cap), }; } const takeableOffers = quote.takeable_offers; // Map API offers → ABI-ready structs via helper const takes = takeableOffers.map((t) => ({ offer: apiOfferToAbi(t.offer), ratifierData: t.ratifier_data, units: BigInt(t.units), })); ``` ### SDK [#sdk] `MidnightApi.fetchBookQuote` wraps the quote endpoint as part of the Morpho SDK's Midnight API helpers and returns ABI-ready take objects #### Step 1: Install dependencies [#step-1-install-dependencies] ```javascript npm install @morpho-org/morpho-sdk viem ``` #### Step 2: Get ABI-ready takeable offers [#step-2-get-abi-ready-takeable-offers] ```typescript import { MidnightApi, type MidnightApiTake } from "@morpho-org/morpho-sdk/midnight-api"; import { parseUnits } from "viem"; const marketId = "0xd92de5e7fbb...7614"; // from browsing books in Step 1 // SDK wraps the quote endpoint and maps the response into ABI-ready take objects (uses Morpho's router by default, no config needed) const quote = await MidnightApi.fetchBookQuote({ marketId, side: "bids", // borrower takes bids assets: parseUnits("10000", 6), // 10,000 USDC to borrow slippage: "0.5", // 0.5% slippage guard }); // quote.data contains: // .averageBestPrice (BigInt) // .averageWorstPrice (BigInt) // .availableAssets (BigInt) // .availableUnits (BigInt) // .takeableOffers (MidnightApiTake[], ABI-ready) // ABI-ready takes const takes: MidnightApiTake[] = quote.data.takeableOffers; // Compute your borrowing cost const avgPrice = Number(quote.data.averageBestPrice) / 1e18; const borrowAPR = (1 / avgPrice - 1) * (365 * 86400) / ttm; ``` ## **Execute the borrow** [#execute-the-borrow] The recommended path is the [Morpho SDK](/developers/sdks/morpho-sdk/): it derives the collateral (WETH) approval and the one-time `MidnightBundles` authorization for you, applies your rate guard, and builds the atomic supply + borrow bundle. The raw contract call below it is the manual equivalent. Either way, hold sufficient collateral to cover the position at the market's LLTV. ### Morpho SDK [#morpho-sdk] Extend the wallet client from "Wallet setup" and hand the flow the quote from the previous step (SDK tab): ```typescript import { parseUnits, publicActions } from "viem"; import { morphoViemExtension } from "@morpho-org/morpho-sdk"; // Extend the wallet client with the `morpho` namespace and the public reads // the flow needs (getBlock / waitForTransactionReceipt). const client = walletClient.extend(morphoViemExtension()).extend(publicActions); const midnight = client.morpho.midnight(8453); // Fresh, same-block market snapshot. const block = await client.getBlock(); const marketData = await midnight.getMarketData(marketId, { blockNumber: block.number, }); // Rate guard: cap the units (debt at maturity) you're willing to sell for // 10,000 USDC — if the price drops below 0.95, the bundle reverts. const worstAcceptablePrice = 0.95; const maxUnits = BigInt(Math.ceil(10_000e6 / worstAcceptablePrice)); // Deadline: the transaction reverts if not included before this timestamp const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now // Supply 5 WETH collateral and borrow 10,000 USDC atomically. `collateralIndex` // defaults to 0n — the market's first configured collateral (WETH here). const supplyCollateralTakeBorrow = midnight.supplyCollateralTakeBorrow({ accountAddress: taker, marketData, collateralAssets: parseUnits("5", 18), loanAssets: parseUnits("10000", 6), maxUnits, takeableOffers: quote.data.takeableOffers, deadline, }); // Requirements are plain transactions — the WETH approval to MidnightBundles // plus the one-time MidnightBundles authorization. The SDK returns only the // ones that are still missing; send each and wait for its receipt. for (const requirement of await supplyCollateralTakeBorrow.getRequirements()) { const hash = await client.sendTransaction(requirement); await client.waitForTransactionReceipt({ hash }); } const hash = await client.sendTransaction(supplyCollateralTakeBorrow.buildTx()); console.log("Borrow tx:", hash); ``` See [Taker flows](/developers/sdks/morpho-sdk/midnight/#taker-flows) for the full surface, including the borrow-only `takeBorrow`. ### Raw contract call [#raw-contract-call] Working below the SDK? Call the `MidnightBundles` contract directly — it fills toward your target in one transaction, skipping stale offers automatically, but you handle the approvals and authorization yourself. **Prerequisites** The taker must: 1. Authorize `MidnightBundles` on the Midnight contract. 2. Approve `MidnightBundles` to pull the **collateral token** (e.g. WETH). 3. Hold sufficient collateral to cover the position at the market's LLTV. #### Authorizations & Approvals [#authorizations--approvals] Pick between Path 1, Path 2, or Path 3 depending on your UX and signing needs. ```typescript import { erc20Abi, maxUint256, createPublicClient, encodeAbiParameters, parseAbiParameters, parseSignature, parseUnits, http, } from "viem"; import { base } from "viem/chains"; const MIDNIGHT_BUNDLES = "0x091183d729BE9f808c212b475E387A12E67850A7"; // MidnightBundles on Base (from the @morpho-org/morpho-ts address registry) const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base const WETH = "0x4200000000000000000000000000000000000006"; // WETH on Base const MIDNIGHT = "0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A"; // Midnight on Base (from the registry) // Path 1: approve() // 1. Let MidnightBundles pull WETH during the buy // 2. maxUint256 = standing allowance await walletClient.writeContract({ account: taker, address: WETH, abi: erc20Abi, functionName: "approve", args: [MIDNIGHT_BUNDLES, maxUint256], }); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const collateralPermitNone = { kind: 0, data: "0x" } as const; // -------------------------------------------------------------- // Path 2: EIP-2612 // For a permit-capable COLLATERAL token only (WETH has no permit() but example assumes COLLATERAL of choise has permit()) const COLLATERAL = "0x..."; // a permit-capable collateral token const collateralAmount = parseUnits("1", 18); // 1 unit of collateral const publicClient = createPublicClient({ chain: base, transport: http() }); const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Read the taker's current EIP-2612 nonce from the USDC contract const nonce = await publicClient.readContract({ address: COLLATERAL, abi: [{ name: "nonces", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] }], functionName: "nonces", args: [taker], }); const sig = await walletClient.signTypedData({ account: taker, // TODO: Replace token name and version based on the COLLATERAL choice domain: { name: "", version: "", chainId: 8453, verifyingContract: COLLATERAL }, types: { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ] }, primaryType: "Permit", message: { owner: taker, spender: MIDNIGHT_BUNDLES, value: collateralAmount, nonce, deadline }, }); const { r, s, v } = parseSignature(sig); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const collateralPermitERC2612 = { kind: 1, // 1 = ERC2612 data: encodeAbiParameters( parseAbiParameters("uint256 deadline, uint8 v, bytes32 r, bytes32 s"), [deadline, Number(v), r, s], ), } as const; // -------------------------------------------------------------- // Path 3: Permit2 // One-time setup (once per token, ever): approve Permit2 on collateral asset (i.e. WETH) const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // One-time setup: approve Permit2 on WETH. After this, every future authorization is just a signature await walletClient.writeContract({ account: taker, address: WETH, abi: erc20Abi, functionName: "approve", args: [PERMIT2, maxUint256], }); // Per-borrow: sign a Permit2 SignatureTransfer (offchain, no gas) const nonce = BigInt(Date.now()); // unordered nonce (Permit2 uses a bitmap) const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); const sig = await walletClient.signTypedData({ account: taker, domain: { name: "Permit2", chainId: 8453, verifyingContract: PERMIT2 }, types: { PermitTransferFrom: [ { name: "permitted", type: "TokenPermissions" }, { name: "spender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], TokenPermissions: [ { name: "token", type: "address" }, { name: "amount", type: "uint256" }, ], }, primaryType: "PermitTransferFrom", message: { permitted: { token: WETH, amount: collateralAmount }, spender: MIDNIGHT_BUNDLES, nonce, deadline, }, }); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const collateralPermit2 = { kind: 2, // 2 = Permit2 data: encodeAbiParameters( parseAbiParameters("uint256 nonce, uint256 deadline, bytes signature"), [nonce, deadline, sig], ), } as const; // -------------------------------------------------------------- // Authorize MidnightBundles to act on your position (one-time boolean, persists across future actions) await walletClient.writeContract({ account: taker, address: MIDNIGHT, abi: midnightAbi, functionName: "setIsAuthorized", args: [MIDNIGHT_BUNDLES, true, taker], }); ``` #### Execute on-chain [#execute-on-chain] Specify the target amount of assets you want to sell (i.e. borrow), a price guard, the collateral you want to post, and the bundler's arguments. ```typescript import { createWalletClient, http, parseUnits, zeroAddress, maxUint256 } from "viem"; import { base } from "viem/chains"; import { midnightBundlesAbi } from "@morpho-org/morpho-sdk/abis"; const MIDNIGHT_BUNDLES = "0x091183d729BE9f808c212b475E387A12E67850A7"; // MidnightBundles on Base (from the @morpho-org/morpho-ts address registry) const WETH = "0x4200000000000000000000000000000000000006"; // WETH contract address // Borrower sells units to receive USDC, depositing WETH as collateral const targetSellerAssets = parseUnits("10000", 6); // want 10,000 USDC const worstAcceptablePrice = 0.95; // worst price per unit you'll accept // Calculate maxUnits to set your own price guard (cap your debt obligation by specifying the maximum number of units you're willing to sell) from worst acceptable borrow rate // If price drops below 0.95, you'd need to sell more units than this so the bundle reverts instead of giving you a worse rate const maxUnits = BigInt(Math.ceil(10_000e6 / worstAcceptablePrice)); // Calculate collateral needed for the debt you want to take using the market's LLTV, and the oracle price of the collateral asset: // 1. Retrieve the collateral's LLTV from the "Browse markets" step: collaterals[0].lltv = "770000000000000000" → 77% (WETH's LLTV for this market) // 2. Fetch your desireable debt at maturity (maxUnits ≈ 10,527 USDC) // 3. Estimate the minimum collateral value needed for this configuration (debt / LLTV = 10,527 / 0.77 ≈ 13,671 USDC) // 4. At WETH ≈ $3,000, minimum debt for this position is ≈ 4.56 WETH (at the liquidation boundary) // 5. Add a safety buffer (i.e. ~10% headroom above minimum) on that to get to 5 WETH const collateralAmount = parseUnits("5", 18); // 5 WETH collateral // Deadline: the transaction reverts if not included before this timestamp const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now // Collateral is referenced by index into the market's collateralParams[] NOT by token address. // Here collateralIndex 0 = collateralParams[0] = WETH. // See Authorizations section for different permit inputs (i.e. collateralPermitNone, collateralPermitERC2612, collateralPermit2) // The permit is per collateral asset and per-supply action; { kind: 0 } means "already approved on the bundle". const collateralSupplies = [ { collateralIndex: 0n, assets: collateralAmount, permit: collateralPermitNone }, ]; const hash = await walletClient.writeContract({ account: taker, address: MIDNIGHT_BUNDLES, abi: midnightBundlesAbi, functionName: "midnightBundlesV1SupplyCollateralAndSellWithAssetsTarget", args: [ targetSellerAssets, // USDC to receive maxUnits, // revert if more units sold than this (rate protection) taker, // taker — the borrower's address false, // reduceOnly (false for a new borrow position) taker, // receiver (where the borrowed USDC is sent) collateralSupplies, // CollateralSupply[] — WETH to deposit as collateral takes, // takes (takeable offers from the quote) 0n, // referralFeePct (0 = none) zeroAddress, // referralFeeRecipient (none) maxUint256, // maxContinuousFee (type(uint256).max disables the check) deadline // deadline (reverts if tx is included after this timestamp) ], }); console.log("Borrow tx:", hash); ``` --- # Exit borrow at maturity Source: https://docs.morpho.org/developers/midnight/tutorials/exit-borrow-by-repaying-in-full-at-maturity {/* AGENT-GENERATED from exit-borrow-by-repaying-in-full-at-maturity.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} ## **Wallet setup** [#wallet-setup] Define the wallet client and the borrower’s address. This is the account that will sign transactions and receive units at maturity. ```typescript import { createWalletClient, http, parseUnits, zeroAddress, type Address, } from "viem"; import { base } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; // The existing borrower (your wallet address) const borrower: Address = "0xYourWalletAddress"; // Create a wallet client connected to Base // This example uses a simple EOA with a private key for brevity // You can substitute in any wallet solution that fits your setup const walletClient = createWalletClient({ account: privateKeyToAccount("0xYourPrivateKey"), chain: base, transport: http(), }); ``` ## **Read outstanding debt & collateral** [#read-outstanding-debt--collateral] In order to exit your position at maturity, repay your debt in full. Collateral can only be released in full once the debt is fully repaid. To fetch a user's outstanding debt and locked collateral, use the following query: ```javascript GET https://api.morpho.org/v0/midnight/markets /{marketId}/users/{userAddress}/position // Response { "data": { "chain_id": 8453, "market_id": "0xd92de5e7fbb...7614", // market the borrower borrowed from "user_address": "0xYourWalletAddress", "last_indexed_block": "47457420", "loan_token": "0x8335...2913", // USDC "maturity": "1798761600", "type": "borrow", // "lend" | "borrow" | "collateral_only" "credit": "0", // lender side (0 for a borrower) "debt": "10527000000", // outstanding debt to clear "pending_fee": "0", // relevant for type: "credit" (over the defined maturity date, here's what you'll owe as a lender) "last_loss_factor": "0", "loss_factor": "0", "collaterals": [ // each entry is { token, amount } { "token": "0x4200000000000000000000000000000000000006", // WETH "amount": "5000000000000000000" // 5 WETH (18 dec) } ] } } ``` Positions with zero credit and debt but non-empty collaterals are returned as `type`: "collateral\_only". ## Repay & withdraw collateral [#repay--withdraw-collateral] A full collateral withdrawal only succeeds once the position's debt is zero. If accrual leaves even 1 unit unpaid, the withdrawal of the corresponding collateral reverts (it would push the position under its LLTV). Over-approving slightly plus a small repay buffer is what makes the at-maturity exit reliably clean. The recommended path is the [Morpho SDK](/developers/sdks/morpho-sdk/): it derives the USDC approval and the one-time `MidnightBundles` authorization for you and builds the atomic repay + withdraw bundle. The raw contract call below it is the manual equivalent. ### **Morpho SDK** [#morpho-sdk] Extend the wallet client from "Wallet setup" and reuse the position read from the previous step: ```typescript import { parseUnits, publicActions } from "viem"; import { morphoViemExtension } from "@morpho-org/morpho-sdk"; // Extend the wallet client with the `morpho` namespace and the public reads // the flow needs (getBlock / waitForTransactionReceipt). const client = walletClient.extend(morphoViemExtension()).extend(publicActions); const midnight = client.morpho.midnight(8453); const marketId = position.market_id; // from "Read outstanding debt & collateral" // Fresh, same-block market snapshot. const block = await client.getBlock(); const marketData = await midnight.getMarketData(marketId, { blockNumber: block.number, }); // Full obligation = debt + accrued pending fee. Add a small buffer if accrual // between reading and executing could leave dust (see the note above). const repayAssets = BigInt(position.debt) + BigInt(position.pending_fee); // Release the full WETH balance. `collateralIndex` defaults to 0n — the // market's first configured collateral. const withdrawCollateralAssets = BigInt(position.collaterals[0].amount); // Deadline: the transaction reverts if not included before this timestamp const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now // Repay and withdraw atomically in one MidnightBundles bundle. const repayWithdrawCollateral = midnight.repayWithdrawCollateral({ accountAddress: borrower, marketData, repayAssets, withdrawCollateralAssets, deadline, }); // Requirements are plain transactions — the USDC approval to MidnightBundles // (returned only while repayAssets > 0) plus the one-time MidnightBundles // authorization. Send each and wait for its receipt. for (const requirement of await repayWithdrawCollateral.getRequirements()) { const hash = await client.sendTransaction(requirement); await client.waitForTransactionReceipt({ hash }); } const hash = await client.sendTransaction(repayWithdrawCollateral.buildTx()); console.log("Repay + withdraw tx:", hash); ``` See [Position maintenance](/developers/sdks/morpho-sdk/midnight/#position-maintenance) for the full surface, including `redeem` and `cancelOffer`. ### **Raw contract call: Authorizations & Approvals** [#raw-contract-call-authorizations--approvals] **Prerequisites** The borrower must have: 1. Authorized `MidnightBundles` on the Midnight contract (so it can act on the positio on their behalf). 2. Approved `MidnightBundles` to pull the loan token Pick between Path 1, Path 2, or Path 3 depending on your UX and signing needs. ```typescript import { erc20Abi, createPublicClient, encodeAbiParameters, parseAbiParameters, parseSignature, parseUnits, http, } from "viem"; import { base } from "viem/chains"; const MIDNIGHT_BUNDLES = "0x091183d729BE9f808c212b475E387A12E67850A7"; // MidnightBundles on Base (from the @morpho-org/morpho-ts address registry) const MIDNIGHT = "0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A"; // Midnight on Base (from the registry) const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base // Full obligation = debt + accrued pending_fee const debtAssets = BigInt(position.debt) + BigInt(position.pending_fee); // Approve a bit more than the debtAssets. Any accrual between reading and executing could leave dust that blocks full collateral release. const approveAmount = debtAssets + parseUnits("1", 6); // Path 1: approve() // 1. Let MidnightBundles pull USDC // 2. Set limit based on approveAmount computed above await walletClient.writeContract({ account: borrower, address: USDC, abi: erc20Abi, functionName: "approve", args: [MIDNIGHT_BUNDLES, approveAmount], }); // Variable to set loanTokenPermit later in bundler argument const tokenPermitNone = { kind: 0, data: "0x" } as const; // -------------------------------------------------------------- // Path 2: EIP-2612 // Authorize exactly the USDC you'll spend buying units const publicClient = createPublicClient({ chain: base, transport: http() }); // Ceiling you authorize via the permit (covers fee accruing before the tx lands) // Same as "approvedAmount" defined above for the approval flow const permitValue = BigInt(position.debt) + BigInt(position.pending_fee) + parseUnits("1", 6); const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // Read the owner's current EIP-2612 nonce from the token. const nonce = await publicClient.readContract({ address: USDC, abi: [{ name: "nonces", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] }], functionName: "nonces", args: [borrower], }); // Offchain signature (no transaction, no gas) const sig = await walletClient.signTypedData({ account: borrower, domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: USDC }, types: { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ] }, primaryType: "Permit", message: { owner: borrower, spender: MIDNIGHT_BUNDLES, value: permitValue, nonce, deadline }, }); const { r, s, v } = parseSignature(sig); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const loanTokenPermitEIP2612 = { kind: 1, // 1 = ERC2612 data: encodeAbiParameters( parseAbiParameters("uint256 deadline, uint8 v, bytes32 r, bytes32 s"), [deadline, Number(v), r, s], ), } as const; // -------------------------------------------------------------- // Path 3: Permit2 // One-time setup (once per token, ever): approve Permit2 on USDC const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // canonical, same on every chain const repayCeiling = BigInt(position.debt) + BigInt(position.pending_fee) + parseUnits("1", 6); // One-time setup (once per token, ever): approve Permit2 on USDC. await walletClient.writeContract({ account: borrower, address: USDC, abi: erc20Abi, functionName: "approve", args: [PERMIT2, maxUint256], }); // Per-repay: sign a Permit2 SignatureTransfer (offchain, no gas). const nonce = BigInt(Date.now()); // unordered nonce (Permit2 uses a bitmap) const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); const sig = await walletClient.signTypedData({ account: borrower, domain: { name: "Permit2", chainId: 8453, verifyingContract: PERMIT2 }, types: { PermitTransferFrom: [ { name: "permitted", type: "TokenPermissions" }, { name: "spender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], TokenPermissions: [ { name: "token", type: "address" }, { name: "amount", type: "uint256" }, ], }, primaryType: "PermitTransferFrom", message: { permitted: { token: USDC, amount: repayCeiling }, spender: MIDNIGHT_BUNDLES, nonce, deadline, }, }); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const loanTokenPermit2 = { kind: 2, // 2 = Permit2 data: encodeAbiParameters( parseAbiParameters("uint256 nonce, uint256 deadline, bytes signature"), [nonce, deadline, sig], ), } as const; // -------------------------------------------------------------- // Authorize MidnightBundles to act on your position (one-time boolean, persists across future actions) await walletClient.writeContract({ account: borrower, address: MIDNIGHT, abi: midnightAbi, functionName: "setIsAuthorized", args: [MIDNIGHT_BUNDLES, true, borrower], }); ``` ### **Raw contract call: Execute on-chain** [#raw-contract-call-execute-on-chain] ```typescript import { midnightBundlesAbi } from "@morpho-org/morpho-sdk/abis"; // Release the full balance of each collateral: // The position lists collateral by { token, amount } and the contract wants an INDEX into market.collateralParams[], so resolve token → index here. const collateralWithdrawals = position.collaterals.map((c: any) => ({ collateralIndex: BigInt( market.collateralParams.findIndex( (cp: any) => cp.token.toLowerCase() === c.token.toLowerCase() ) ), assets: BigInt(c.amount), })); const repayAssets = BigInt(position.debt) + BigInt(position.pending_fee); // Deadline: the transaction reverts if not included before this timestamp const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now const hash = await walletClient.writeContract({ account: borrower, address: MIDNIGHT_BUNDLES, abi: midnightBundlesAbi, functionName: "midnightBundlesV1RepayAndWithdrawCollateral", args: [ market, // Market struct (fetch via GET /v0/midnight/markets/{marketId} or reuse from browsing) repayAssets, // loan tokens to repay (full debt) borrower, // onBehalf — whose position is repaid tokenPermitNone, // if Permit EIP-2612 or Permit2 is used use loanTokenPermitEIP2612 or loanTokenPermit2, respectively collateralWithdrawals, // CollateralWithdrawal[] (release all collateral) borrower, // collateralReceiver (where the WETH is sent) 0n, // referralFeePct (0 = none) zeroAddress, // referralFeeRecipient (none) deadline // deadline (reverts if tx is included after this timestamp) ], }); console.log("Repay + withdraw tx:", hash); ``` --- # Lend at a fixed rate Source: https://docs.morpho.org/developers/midnight/tutorials/lend-at-a-fixed-rate {/* AGENT-GENERATED from lend-at-a-fixed-rate.mdxai using skills/generate-docs/SKILL.md. Edit the scaffold and ask an agent to regenerate. Do not edit this file directly. */} ## **Wallet setup** [#wallet-setup] Define the wallet client and the lender's address. This is the account that will sign transactions and receive units at maturity. ```typescript import { createWalletClient, http, parseUnits, zeroAddress, type Address, } from "viem"; import { base } from "viem/chains"; import { privateKeyToAccount } from "viem/accounts"; // The lender (taker of asks), i.e. your wallet address const taker: Address = "0xYourWalletAddress"; // Create a wallet client connected to Base // This example uses a simple EOA with a private key for brevity // You can substitute in any wallet solution that fits your setup const walletClient = createWalletClient({ account: privateKeyToAccount("0xYourPrivateKey"), chain: base, transport: http(), }); ``` ## **Browse markets** [#browse-markets] Fetch the list of books filtered by chain and loan token. Each book represents one market with its top ask/bid price levels: ```javascript GET https://api.morpho.org/v0/midnight/books ?chain_ids=8453 // Base &loan_tokens=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 // USDC on Base &sort=maturity // Response (each book is one market with its top ask/bid levels) { "cursor": "eyJzb3J0Ijpb...", // null when there is no next page "data": [ { "market_id": "0xd92de5e7fbb...7614", // use this ID in the next step "chain_id": 8453, "midnight": "0x2F7a3AA739ba5792Ce1b4eA046117f2C0095BCA6", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "collaterals": [ { "token": "0x4200000000000000000000000000000000000006", // WETH on Base "lltv": "770000000000000000", // 77% LTV "liquidation_cursor": "250000000000000000", "oracle": "0xD09048c8B568Dbf5f189302beA26c9edABFC4858" } ], "maturity": 1798761600, // January 1, 2027 "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000", // ┌─────────────────────────────────────────────────────────┐ // │ FOR LENDING → read the ASKS │ // │ Asks = makers selling units = borrowers seeking loans │ // │ You (the lender) take these asks = buy their units │ // │ The ask price is what you pay per unit → your rate │ // └─────────────────────────────────────────────────────────┘ "asks": [ { "tick": 42, "price": "952380952380952380", "units": "10000000000", "assets": "9523809523", "count": 3 }, { "tick": 43, "price": "948000000000000000", "units": "18500000000", "assets": "17538000000", "count": 5 } ], // (bids = lenders' buy-offers, read these for borrowing) "bids": [ { "tick": 41, "price": "956937000000000000", "units": "8000000000", "assets": "7655497000", "count": 2 } ] } ] } ``` ## **Select market & estimate lend rate** [#select-market--estimate-lend-rate] The best ask is the cheapest unit price. Convert the price into an annualized rate: ```typescript // Select book data const book = response.data[0]; // Obtain best ask price (scaled by 1e18) const bestAskPrice = Number(book.asks[0].price) / 1e18; // ≈ 0.9524 // Calculate time to maturity (ttm) in seconds const ttm = book.maturity - Date.now() / 1000; // Calculate fixed lend APR // Note: This assumes the entire lend size fits within that first tick's available units. // If lend size is higher than the assets at that tick, the effective blended rate would be lower. // The Morpho router (next step) returns the accurate blended price stemming from all takeable offers available. const lendAPR = (1 / bestAskPrice - 1) * (365 * 86400) / ttm; // ≈ 5.2% console.log(`Lend APR: ${(lendAPR * 100).toFixed(2)}%`); // If the rate looks good, save the market ID for the next step const marketId = book.market_id; // "0xd92de5e7fbb...7614" ``` ## **Get a quote for your offer** [#get-a-quote-for-your-offer] ### API [#api] #### Step 1: Get a quote [#step-1-get-a-quote] Specify the amount of USDC you want to lend to a market with slippage. The Morpho router (via the quote API shown below) returns executable takeable offers with fallback liquidity (i.e. additional offers returned in case offers are partially consumed). This way, during execution, the bundle can skip stale offers and still fill the lender's request. ```typescript GET https://api.morpho.org/v0/midnight/books/{marketId}/asks/quote ?assets=10000000000 // 10,000 USDC (6 decimals) &slippage=0.5 // 0.5% slippage guard &limit=10000 // max offers walked/returned; default and maximum 10,000 // Response: the router's execution plan: { "data": { "average_best_price": "947619047619047619", // realized target fill using onchain per-offer rounding, rounded against the taker "average_worst_price": "952300000000000000", // conservative adverse-placement bound across returned caps; not the slippage-derived guard "available_assets": "15000000000", // max assets in this returned limit window "available_units": "15750000000", // max units in this returned limit window "takeable_offers": [ { "market_id": "0x56b822d7d8be6bded8e1782277989fe5dc4e5b0a4f7f9d349482bc933bd5436c", "units": "5250000000", // maximum this offer may contribute, not an exact allocation "offer": { "market": { "chain_id": 8453, "midnight": "0x2F7a3AA739ba5792Ce1b4eA046117f2C0095BCA6", "loan_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "collaterals": [ { "token": "0x4200000000000000000000000000000000000006", "lltv": "860000000000000000", "liquidation_cursor": "300000000000000000", "oracle": "0xD09048c8B568Dbf5f189302beA26c9edABFC4858" } ], "maturity": 1786147200, "rcf_threshold": "0", "enter_gate": "0x0000000000000000000000000000000000000000", "liquidator_gate": "0x0000000000000000000000000000000000000000" }, "buy": false, // sell offer (ask): the maker is borrowing "maker": "0x7b093658BE7f90B63D7c359e8f408e503c2D9401", "max_units": "0", "start": 1761922790, "expiry": 1786147200, "tick": 42, "group": "0x000000000000000000000000000000000000000000000000000000000008b8f4", "callback": "0x0000000000000000000000000000000000000000", "callback_data": "0x", "receiver_if_maker_is_seller": "0x7b093658BE7f90B63D7c359e8f408e503c2D9401", "ratifier": "0x0000000000000000000000000000000000000002", "reduce_only": false, "max_assets": "5000000000", "continuous_fee_cap": "115792089237316195423570985008687907853269984665640564039457584007913129639935" }, "ratifier_data": "0x4a8b...f201" } // Additional complete offers follow in best-price order as fallback excess. // A grouped offer's full cap is not guaranteed: sibling fills may consume // group capacity before execution, which is why the router returns fallbacks. ] } } // Interpreting the quote const quote = response.data; const avgPrice = Number(quote.average_best_price) / 1e18; // ≈ 0.9476 const impliedRate = (1 / avgPrice - 1) * (365 * 86400) / ttm; const targetUnits = BigInt(Math.ceil(10_000e6 / avgPrice)); // target fill; available_units is fallback-window capacity ``` #### Step 2: Convert takeable offers to ABI-encoded data [#step-2-convert-takeable-offers-to-abi-encoded-data] The API returns snake\_case JSON, whereas the onchain ABI expects camelCase tuples. The `apiOfferToAbi` helper converts the API offer to an ABI struct. ```typescript function apiOfferToAbi(apiOffer: any) { return { market: { chainId: BigInt(apiOffer.market.chain_id), midnight: apiOffer.market.midnight, loanToken: apiOffer.market.loan_token, collateralParams: apiOffer.market.collaterals.map((c: any) => ({ token: c.token, lltv: BigInt(c.lltv), liquidationCursor: BigInt(c.liquidation_cursor), oracle: c.oracle, })), maturity: BigInt(apiOffer.market.maturity), rcfThreshold: BigInt(apiOffer.market.rcf_threshold), enterGate: apiOffer.market.enter_gate, liquidatorGate: apiOffer.market.liquidator_gate, }, buy: apiOffer.buy, maker: apiOffer.maker, maxUnits: BigInt(apiOffer.max_units), maxAssets: BigInt(apiOffer.max_assets), start: BigInt(apiOffer.start), expiry: BigInt(apiOffer.expiry), tick: BigInt(apiOffer.tick), group: apiOffer.group, callback: apiOffer.callback, callbackData: apiOffer.callback_data, receiverIfMakerIsSeller: apiOffer.receiver_if_maker_is_seller, ratifier: apiOffer.ratifier, reduceOnly: apiOffer.reduce_only, continuousFeeCap: BigInt(apiOffer.continuous_fee_cap), }; } // Takeable offers from quote API const takeableOffers = quote.takeable_offers; // Map offers to ABI-ready structs via helper const takes = takeableOffers.map((t) => ({ offer: apiOfferToAbi(t.offer), ratifierData: t.ratifier_data, units: BigInt(t.units), })); ``` ### SDK [#sdk] `MidnightApi.fetchBookQuote` wraps the quote endpoint as part of the Morpho SDK's Midnight API helpers and returns ABI-ready take objects #### Step 1: Install dependencies [#step-1-install-dependencies] ```javascript npm install @morpho-org/morpho-sdk viem ``` #### Step 2: Get ABI-ready takeable offers [#step-2-get-abi-ready-takeable-offers] ```typescript import { MidnightApi, type MidnightApiTake } from "@morpho-org/morpho-sdk/midnight-api"; import { parseUnits } from "viem"; const marketId = "0xd92de5e7fbb...7614"; // from the "Browse markets" step // SDK wraps the quote endpoint and maps the response into ABI-ready take objects (uses Morpho's router by default, no config needed) const quote = await MidnightApi.fetchBookQuote({ marketId, side: "asks", // lender takes asks (not bids) assets: parseUnits("10000", 6), // 10,000 USDC to lend slippage: "0.5", // 0.5% slippage guard }); // quote.data contains: // .averageBestPrice (BigInt) // .averageWorstPrice (BigInt) // .availableAssets (BigInt) // .availableUnits (BigInt) // .takeableOffers (MidnightApiTake[], ABI-ready) // ABI-ready takes const takes: MidnightApiTake[] = quote.data.takeableOffers; ``` ## **Execute the lend** [#execute-the-lend] The recommended path is the [Morpho SDK](/developers/sdks/morpho-sdk/): it derives the USDC approval and the one-time `MidnightBundles` authorization for you, applies your price guard, and builds the bundle transaction that fills toward your target while skipping stale offers. The raw contract call below it is the manual equivalent. ### Morpho SDK [#morpho-sdk] Extend the wallet client from "Wallet setup" and hand the flow the quote from the previous step (SDK tab): ```typescript import { parseUnits, publicActions } from "viem"; import { morphoViemExtension } from "@morpho-org/morpho-sdk"; // Extend the wallet client with the `morpho` namespace and the public reads // the flow needs (getBlock / waitForTransactionReceipt). const client = walletClient.extend(morphoViemExtension()).extend(publicActions); const midnight = client.morpho.midnight(8453); // Fresh, same-block market snapshot. const block = await client.getBlock(); const marketData = await midnight.getMarketData(marketId, { blockNumber: block.number, }); // Price guard: cap the minimum units you'll accept for your 10,000 USDC — // if the average fill price rises above 0.96, the bundle reverts. const worstAcceptablePrice = 0.96; const minUnits = BigInt(Math.ceil(10_000e6 / worstAcceptablePrice)); // Deadline: the transaction reverts if not included before this timestamp const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now // takeLend spends `assets` of USDC for at least `minUnits` of credit units. const takeLend = midnight.takeLend({ accountAddress: taker, marketData, assets: parseUnits("10000", 6), minUnits, takeableOffers: quote.data.takeableOffers, deadline, }); // Requirements are plain transactions — the USDC approval to MidnightBundles // plus the one-time MidnightBundles authorization. The SDK returns only the // ones that are still missing; send each and wait for its receipt. for (const requirement of await takeLend.getRequirements()) { const hash = await client.sendTransaction(requirement); await client.waitForTransactionReceipt({ hash }); } const hash = await client.sendTransaction(takeLend.buildTx()); console.log("Lend tx:", hash); ``` See [Taker flows](/developers/sdks/morpho-sdk/midnight/#taker-flows) for the full surface, including `takeBorrow` and `supplyCollateralTakeBorrow`. ### Raw contract call [#raw-contract-call] Working below the SDK? Call the `MidnightBundles` contract directly — it fills toward your target in one transaction, skipping stale offers automatically, but you handle the approvals and authorization yourself. **Prerequisites** 1. Approve `MidnightBundles` contract to pull loan token (i.e. USDC) 2. Authorize `MidnightBundles` on the Midnight contract. #### Authorizations & Approvals [#authorizations--approvals] Pick between Path 1, Path 2, or Path 3 depending on your UX and signing needs. ```typescript import { erc20Abi, maxUint256, createPublicClient, encodeAbiParameters, parseAbiParameters, parseSignature, parseUnits, http, } from "viem"; import { base } from "viem/chains"; const MIDNIGHT_BUNDLES = "0x091183d729BE9f808c212b475E387A12E67850A7"; // MidnightBundles on Base (from the @morpho-org/morpho-ts address registry) const USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; // USDC on Base const MIDNIGHT = "0xAdedD8ab6dE832766Fedf0FaC4992E5C4D3EA18A"; // Midnight on Base (from the registry) // Path 1: approve() // 1. Let MidnightBundles pull USDC during the buy // 2. maxUint256 = standing allowance await walletClient.writeContract({ account: taker, address: USDC, abi: erc20Abi, functionName: "approve", args: [MIDNIGHT_BUNDLES, maxUint256], }); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const tokenPermitNone = { kind: 0, data: "0x" } as const; // -------------------------------------------------------------- // Path 2: EIP-2612 (Permit) // Authorize exactly the USDC you'll spend buying units const permitValue = parseUnits("10000", 6); // = targetBuyerAssets (10,000 USDC) const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour expiry const publicClient = createPublicClient({ chain: base, transport: http() }); // Read the taker's current EIP-2612 nonce from the USDC contract const nonce = await publicClient.readContract({ address: USDC, abi: [{ name: "nonces", type: "function", stateMutability: "view", inputs: [{ name: "owner", type: "address" }], outputs: [{ type: "uint256" }] }], functionName: "nonces", args: [taker], }); // Offchain signature (no transaction, no gas) const sig = await walletClient.signTypedData({ account: taker, domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: USDC }, types: { Permit: [ { name: "owner", type: "address" }, { name: "spender", type: "address" }, { name: "value", type: "uint256" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ] }, primaryType: "Permit", message: { owner: taker, spender: MIDNIGHT_BUNDLES, value: permitValue, nonce, deadline }, }); const { r, s, v } = parseSignature(sig); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const loanTokenPermitERC2612 = { kind: 1, // 1 = ERC-2612 data: encodeAbiParameters( parseAbiParameters("uint256 deadline, uint8 v, bytes32 r, bytes32 s"), [deadline, Number(v), r, s], ), } as const; // -------------------------------------------------------------- // Path 3: Permit2 // One-time setup (once per token, ever): approve Permit2 on USDC const PERMIT2 = "0x000000000022D473030F116dDEE9F6B43aC78BA3"; // canonical, same on every chain const permitValue = parseUnits("10000", 6); // = targetBuyerAssets (10,000 USDC) // One-time setup (once per token, ever): approve Permit2 on USDC. After this, every future authorization is just a signature await walletClient.writeContract({ account: taker, address: USDC, abi: erc20Abi, functionName: "approve", args: [PERMIT2, maxUint256], }); // Per-take: sign a Permit2 SignatureTransfer (offchain, no gas). const nonce = BigInt(Date.now()); // unordered nonce (Permit2 uses a bitmap) const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); const sig = await walletClient.signTypedData({ account: taker, domain: { name: "Permit2", chainId: 8453, verifyingContract: PERMIT2 }, types: { PermitTransferFrom: [ { name: "permitted", type: "TokenPermissions" }, { name: "spender", type: "address" }, { name: "nonce", type: "uint256" }, { name: "deadline", type: "uint256" }, ], TokenPermissions: [ { name: "token", type: "address" }, { name: "amount", type: "uint256" }, ], }, primaryType: "PermitTransferFrom", message: { permitted: { token: USDC, amount: permitValue }, spender: MIDNIGHT_BUNDLES, // contract pulls via Permit2 nonce, deadline, }, }); // Variable to set the loanTokenPermit variable to (bundler argument) in the Execute step that follows const loanTokenPermit2 = { kind: 2, // 2 = Permit2 data: encodeAbiParameters( parseAbiParameters("uint256 nonce, uint256 deadline, bytes signature"), [nonce, deadline, sig], ), } as const; // -------------------------------------------------------------- // Authorize MidnightBundles to act on your position (one-time boolean, persists across future actions) await walletClient.writeContract({ account: taker, address: MIDNIGHT, abi: midnightAbi, functionName: "setIsAuthorized", args: [MIDNIGHT_BUNDLES, true, taker], }); ``` #### Execute on-chain [#execute-on-chain] Specify the target amount of assets you want to buy (i.e. lend), a price guard, and the bundler's arguments. ```typescript import { createWalletClient, http, parseUnits, zeroAddress, maxUint256 } from "viem"; import { base } from "viem/chains"; import { midnightBundlesAbi } from "@morpho-org/morpho-sdk/abis"; const MIDNIGHT_BUNDLES = "0x091183d729BE9f808c212b475E387A12E67850A7"; // MidnightBundles on Base (from the @morpho-org/morpho-ts address registry) const targetBuyerAssets = parseUnits("10000", 6); // 10,000 USDC // Set your own price guard (caps the minimum units you'll receive, preventing you from getting a worse yield than expected) // For example, if avg fill price rises above 0.96 → fewer units → bundle reverts const worstAcceptablePrice = 0.96; const minUnits = BigInt(Math.ceil(10_000e6 / worstAcceptablePrice)); // Deadline: the transaction reverts if not included before this timestamp const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600); // 1 hour from now // The bundle's 5th argument (loanTokenPermit), a TokenPermit { kind, data }, is how you tell the bundle to move your USDC. // The possible values are outlined in "Authorizations & Approvals" step. const hash = await walletClient.writeContract({ account: taker, address: MIDNIGHT_BUNDLES, abi: midnightBundlesAbi, functionName: "midnightBundlesV1BuyWithAssetsTargetAndWithdrawCollateral", args: [ targetBuyerAssets, // how much USDC to spend minUnits, // revert if fewer units returned (yield protection) taker, // taker (the lender's address where the bought units are credited to) false, // reduceOnly (false for a new lend position) tokenPermitNone, // loan-token permit (none = pre-approved) takes, // the takeable offers from the quote (ABI-encoded) [], // collateralWithdrawals (empty for lend) taker, // collateralReceiver (unused — no withdrawals) 0n, // referralFeePct (0 = none) zeroAddress, // referralFeeRecipient (none) maxUint256, // maxContinuousFee (type(uint256).max disables the check) deadline // deadline (reverts if tx is included after this timestamp) ], }); console.log("Lend tx:", hash); ``` --- # Merkl Distribution System Source: https://docs.morpho.org/developers/rewards/concepts/distribution-system [Merkl](https://merkl.xyz/) is a third-party rewards distribution platform that has become the standard for Morpho rewards following [MIP 111](https://snapshot.box/#/s\:morpho.eth/proposal/0xadf3862d97cf4e8bb84a4b3d5117abe1a9db774e92ad97c65bde22214c0b76c9). ## How Merkl Works [#how-merkl-works] ### Offchain Computation [#offchain-computation] Merkl's infrastructure continuously monitors onchain activity and calculates earned rewards: * Monitors onchain activity directly via RPC nodes * Proprietary indexing infrastructure * Real-time event processing This is necessary because: 1. **Complex logic**: Reward calculations involve historical data, forwarding, and blacklisting 2. **Gas efficiency**: Computing onchain would be prohibitively expensive 3. **Flexibility**: Allows for sophisticated distribution strategies ### Merkle Tree Generation & Onchain Distribution [#merkle-tree-generation--onchain-distribution] Every 8 hours, Merkl generates a new Merkle tree containing all claimable rewards. The Merkle root is posted onchain, making rewards claimable. * Rewards are recalculated every 8 hours * Users see their accruing rewards in near real-time ### User Claims [#user-claims] Users can claim their rewards by submitting a transaction with their Merkle proof. * Low waiting period between earning and claiming ## Key Characteristics [#key-characteristics] **Standardized Integration** * Consistent API across all protocols using Merkl * Well-documented claiming process * Community-supported SDKs and tools **Multi-Protocol Support** * Same infrastructure used by many DeFi protocols * Familiar UX for users who've claimed rewards elsewhere * Centralized infrastructure for reward discovery ## When to Use [#when-to-use] Integrate Merkl for: * All new reward programs (July 2025 onwards) * Current MORPHO token distributions * Third-party incentive programs * Real-time reward tracking ## Integration Points [#integration-points] **For Claiming Rewards:** head to the [Integrate Rewards Claim](/developers/rewards/tutorials/claim-rewards) section. **For Displaying Rewards:** head to the [Integrate Rewards Display](/developers/rewards/tutorials/integrate-display) section. --- # Reward Campaigns on Morpho Source: https://docs.morpho.org/developers/rewards/concepts/reward-campaigns Reward campaigns are incentive strategies created via [Merkl](https://merkl.xyz/) to encourage specific user behaviors within the Morpho protocol. Understanding how these campaigns work and the different distribution strategies available is essential for integrators who want to accurately display and calculate reward rates for their users. ## What Are Reward Campaigns? [#what-are-reward-campaigns] Merkl allows protocols, DAOs, and token issuers to **reward users for providing active liquidity** on Morpho. Campaigns incentivize users who hold supply, borrow, or collateral balances on the protocol. Rewards are distributed automatically based on users' positions over time. When creating a campaign through Merkl's interface, you select: * The lending market or vault you want to incentivize * The reward token to distribute * The campaign duration and reward budget * The specific user behavior to reward (supply, borrow, or collateral deposit) It is also possible to incentivize a simple collateral deposit (without the borrow action required). ## Distribution Strategies [#distribution-strategies] Merkl offers two main categories of reward distribution for Morpho: 1. **Vault-Level Incentives** - Target deposits into specific Morpho Vaults 2. **Market-Level Incentives** - Target activity within Morpho Markets ### Vault-Level Incentives [#vault-level-incentives] **Strategy: Supply to a Morpho Vault** This strategy incentivizes users who deposit assets into a specific Morpho Vault. It's the simplest distribution method, rewarding vault depositors proportionally based on their share of total vault deposits over time. **How it works:** * Rewards are distributed linearly over the campaign duration * Users earn rewards based on their vault deposit amount and duration * All depositors into the targeted vault are eligible * Only one reward rate applies (vault supply rewards) **Use cases:** * Bootstrap a new vault to attract initial liquidity * Increase deposits into an established vault * Provide additional yield to vault depositors beyond base APY ### Market-Level Incentives [#market-level-incentives] **Strategy: Incentivize Activity in Morpho Variable Rate Markets** Market-level strategies provide more granular control, allowing you to target specific behaviors within Morpho Variable Rate Markets. You can incentivize: * **Supply**: Reward users who lend assets to a market * **Borrow**: Reward users who borrow assets from a market * **Collateral**: Reward users who deposit collateral in a market, without requiring them to borrow Additionally, you can choose between: * **Specific market targeting**: Incentivize activity in one particular market * **Token-based targeting**: Incentivize use of a specific token across all markets **Distribution Options:** 1. **Supply to a specific market**: Rewards users who supply assets to a particular Morpho Variable Rate Market. 2. **Supply a token to any market**: Rewards users who supply a specific token across all Morpho variable rate markets (e.g., all markets where USDC is the loan asset). 3. **Borrow from a specific market**: Rewards users who borrow assets from a particular Morpho Variable Rate Market. 4. **Borrow a token from any market**: Rewards users who borrow a specific token across all Morpho Variable Rate Markets. 5. **Deposit collateral in a specific market**: Rewards users who deposit collateral in a particular Morpho Variable Rate Market, without requiring a borrow position. 6. **Deposit a token as collateral in any market**: Rewards users who deposit a specific token as collateral across all Morpho Variable Rate Markets, without requiring a borrow position. **How it works:** * Campaigns distribute rewards linearly over a defined period * Each campaign can have separate rates for supply, borrow, and collateral deposits * Users earn rewards based on their position size and duration * Rates are expressed as annualized amounts per token **Use cases:** * Bootstrap a new market by incentivizing early suppliers or borrowers * Encourage borrowing to increase market utilization * Reward specific collateral types to diversify market risk * Create token-wide incentives across the entire Morpho ecosystem ## Combining Multiple Campaigns [#combining-multiple-campaigns] Merkl API provides an APR for each campaign (vault and market ones). Vault depositors may be eligible for rewards from **multiple campaigns simultaneously**: 1. **Direct vault-level rewards**: From campaigns directly targeting the vault 2. **Forwarded market-level rewards**: From campaigns targeting markets where the vault allocates liquidity **Net Vault APY:** ``` Net APY = Native Vault APY + Vault Campaign APR + Σ(Forwarded Market Campaign APR) - Performance Fee - Management Fee ``` Where: * **Native Vault APY**: Native lending yield from the vault's market allocations * **Vault Campaign APR**: Direct vault-level incentives * **Forwarded Market Campaign APR**: Sum of all market-level rewards from allocated markets * **Performance Fee**: Applied only to base APY * **Management Fee**: Applied to total assets deposited in the vault ## Reward Forwarding [#reward-forwarding] One of the most important features of Morpho's reward system is **automatic reward forwarding** for vault depositors. ### How Forwarding Works [#how-forwarding-works] When a Morpho Vault allocates liquidity to a market with an active supply campaign, the vault itself technically earns those rewards (since it's the direct supplier to the market). However, these rewards are **automatically forwarded to individual vault depositors** proportionally to their vault shares. **The forwarding mechanism:** 1. Market-level supply campaigns distribute rewards to suppliers 2. Vaults that supply to these markets earn rewards at the vault contract level 3. Merkl's offchain computation attributes these rewards to individual vault depositors 4. Vault depositors can claim these market-level rewards directly This happens transparently in the background, ensuring that: * Vault depositors benefit from all market-level campaigns * No manual intervention is required * Rewards are distributed fairly based on deposit amounts and duration Forwarding works for Morpho Vault V2 when its underlying allocations are connected to eligible reward campaigns. ### Integration Considerations [#integration-considerations] As an integrator, you should display: * **Direct vault campaigns**: Campaigns explicitly targeting the vault * **Forwarded market campaigns**: Campaigns targeting markets where the vault allocates, with rewards forwarded to depositors * **Combined APR**: Total reward APR from both sources Users expect to see the full picture of their potential earnings, including both direct and forwarded rewards. ## Blacklisting [#blacklisting] Campaign creators can **blacklist specific addresses** from earning rewards. This is commonly used to: * Prevent self-incentivization when protocols bootstrap their own markets * Exclude treasury addresses that provide initial liquidity * Remove addresses that don't align with campaign goals **Example:** If a protocol creates a market and deposits its treasury to provide initial liquidity, it can blacklist its treasury address. This ensures: * Reward budget goes entirely to external users * No circular incentive flow * Fair distribution among community participants Blacklisting is configured when creating the campaign and is enforced during rewards computation. It's transparent-anyone can verify which addresses are excluded from a campaign. ## Historical Context: Legacy Reward Systems [#historical-context-legacy-reward-systems] **Historical Note** Prior to July 2025, Morpho used a native rewards distribution system (the Universal Rewards Distributor or URD) with different program types called "Market Programs" and "Vault Programs." The `rewards.morpho.org` API has been deprecated. Users with unclaimed legacy rewards can look them up and claim via [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). ## Querying Active Campaigns [#querying-active-campaigns] You can see how to retrieve campaigns data in the [Fetch Rewards Data section](/developers/rewards/tutorials/fetch-rewards-data). ## Next Steps [#next-steps] * **Create campaigns**: Follow the [Create a Rewards Program tutorial](/developers/rewards/tutorials/create-program) * **Understand distribution**: Read about [Distribution System](/developers/rewards/concepts/distribution-system) * **Fetch data**: Follow the [Fetch Rewards Data tutorial](/developers/rewards/tutorials/fetch-rewards-data) * **Display rewards**: See the [Integrate Display tutorial](/developers/rewards/tutorials/integrate-display) --- # Complete Integration Example Source: https://docs.morpho.org/developers/rewards/guides/complete-integration This guide provides a complete, working reference implementation for integrating Morpho rewards via Merkl into your application. We'll walk through the **Morpho + Merkl Integration Demo** repository, which demonstrates best practices for production-ready rewards integration. **Example Repository**\ **[morpho-org/merkl-morpho-recipe](https://github.com/morpho-org/merkl-morpho-recipe)** This is an educational Next.js application that showcases how to: * Fetch vault metrics and rewards from Morpho API * Query user claimable rewards from Merkl * Display combined APY (base + rewards) * Implement claim functionality **⚠️ Educational Purpose**: This is a learning resource. Additional security measures, testing, and auditing are required before production use. ## Repository Overview [#repository-overview] ### Tech Stack [#tech-stack] * **Next.js 15** - React framework * **TypeScript** - Type safety * **Morpho API** - GraphQL for vault/market data * **Merkl API** - REST API for Merkl rewards ### Project Structure [#project-structure] ``` src/ ├── app/ # Next.js app router ├── components/ # React components │ ├── VaultMetricsPanel.tsx # Display vault APY + rewards │ ├── UserRewardsPanel.tsx # User claimable rewards │ ├── VaultRewardsDisplay.tsx # Rewards breakdown │ └── ClaimImplementationPanel.tsx # Claim code examples ├── hooks/ # React hooks │ ├── useVaultData.ts # Fetch vault data │ └── useClipboard.ts # Utility hook ├── lib/ # Core logic │ ├── api.ts # API clients │ ├── claiming.ts # Claim functions │ ├── simulation.ts # Transaction simulation │ └── helpers/ │ └── rewards.ts # Reward calculations └── types/ # TypeScript types └── index.ts ``` ## Key Concepts Demonstrated [#key-concepts-demonstrated] ### 1. Combining Morpho and Merkl Data [#1-combining-morpho-and-merkl-data] The demo shows how to fetch and combine data from multiple sources to provide a complete picture: **File: `src/lib/api.ts`** ```typescript export async function getVaultData(vaultAddress: string, chainId: number) { // Fetch from Morpho API const morphoData = await fetchMorphoVault(vaultAddress, chainId); // Extract rewards information const rewards = morphoData.vault.state.allRewards.map((r) => ({ token: r.asset.symbol, apr: parseFloat(r.supplyApr), tokenAddress: r.asset.address, })); // Calculate combined APY const baseApy = parseFloat(morphoData.vault.state.apy); const rewardsApr = rewards.reduce((sum, r) => sum + r.apr, 0); return { vault: morphoData.vault, baseApy, rewardsApr, totalApy: baseApy + rewardsApr, rewards, }; } ``` ### 2. Fetching User Rewards [#2-fetching-user-rewards] **File: `src/lib/helpers/rewards.ts`** ```typescript export async function getUserRewards(userAddress: string, chainId: number) { return fetchMerklUserRewards(userAddress, chainId); } ``` ### 3. Implementing Claims [#3-implementing-claims] **File: `src/lib/claiming.ts`** The demo includes a Merkl claim implementation: **Merkl Claim:** ```typescript export async function claimMerklRewards( userAddress: string, chainId: number, walletClient: WalletClient ) { // 1. Fetch the user's rewards (the endpoint returns an array of per-chain // objects; flatten to a flat list of rewards). const data = await fetch( `https://api.merkl.xyz/v4/users/${userAddress.toLowerCase()}/rewards?chainId=${chainId}` ).then((r) => r.json()); const rewards = data.flatMap((entry) => entry.rewards); // Keep only genuinely-claimable rewards — entries with no proofs or nothing // left to claim make the whole `claim` tx revert with `InvalidProof`. const claimable = rewards.filter( (r) => r.proofs.length > 0 && BigInt(r.amount) > BigInt(r.claimed) ); // Build the parallel, index-aligned arrays the contract expects const users = claimable.map(() => userAddress); const tokens = claimable.map((r) => r.token.address); const amounts = claimable.map((r) => BigInt(r.amount)); // cumulative; pass as-is const proofs = claimable.map((r) => r.proofs); // 2. Execute claim transaction (must be sent from the user's own wallet) const hash = await walletClient.writeContract({ address: MERKL_DISTRIBUTOR_ADDRESS, abi: MERKL_ABI, functionName: "claim", args: [users, tokens, amounts, proofs], }); // 3. Wait for confirmation return await walletClient.waitForTransactionReceipt({ hash }); } ``` **Legacy Rewards**: The `rewards.morpho.org` API has been deprecated. For unclaimed legacy rewards, direct users to [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/). ### 4. React Components for Display [#4-react-components-for-display] **File: `src/components/VaultMetricsPanel.tsx`** ```tsx export function VaultMetricsPanel({ vaultAddress, chainId }) { const { data, loading, error } = useVaultData(vaultAddress, chainId); if (loading) return ; if (error) return ; return (
{/* Vault Header */}

{data.vault.name}

{data.vault.symbol}

{/* APY Display */}
Total APY {data.totalApy.toFixed(2)}%
Base APY: {data.baseApy.toFixed(2)}%
{data.rewardsApr > 0 && (
Rewards: +{data.rewardsApr.toFixed(2)}%
)}
{/* Rewards List */} {data.rewards.length > 0 && ( )}
); } ``` **File: `src/components/UserRewardsPanel.tsx`** ```tsx export function UserRewardsPanel({ userAddress, chainId }) { const [rewards, setRewards] = useState(null); const [claiming, setClaiming] = useState(false); useEffect(() => { getUserRewards(userAddress, chainId).then(setRewards); }, [userAddress, chainId]); const handleClaimAll = async () => { setClaiming(true); try { await claimMerklRewards(userAddress, chainId, walletClient); alert("Rewards claimed successfully!"); // Refresh rewards data getUserRewards(userAddress, chainId).then(setRewards); } catch (error) { console.error("Claim failed:", error); alert("Claim failed. See console for details."); } finally { setClaiming(false); } }; return (

Your Claimable Rewards

{rewards?.map((reward) => (
{reward.token} {formatAmount(reward.amount)} {reward.token}
))}
); } ``` ## Running the Demo [#running-the-demo] ### Prerequisites [#prerequisites] * Node.js 18+ and yarn * A wallet with Morpho positions (for testing user rewards) ### Quick Start [#quick-start] ```bash # Clone the repository git clone https://github.com/morpho-org/merkl-morpho-recipe.git cd merkl-morpho-recipe # Install dependencies yarn install # Run the development server yarn dev # Open http://localhost:3000 ``` ### Explore via Scripts [#explore-via-scripts] The demo includes standalone scripts to understand the data flow: ```bash # See vault APY + rewards calculation yarn demo:yield # Check user claimable rewards yarn demo:rewards # Complete integration flow yarn demo:full ``` **Example Output:** ``` $ yarn demo:yield === Vault Metrics === Vault: Steakhouse USDC Base APY: 8.45% Rewards APR: 2.13% Total APY: 10.58% Reward Programs: - MORPHO: 1.85% APR - STEAK: 0.28% APR ``` ## Key Takeaways from the Demo [#key-takeaways-from-the-demo] ### 1. Error Handling [#1-error-handling] The demo implements graceful error handling: ```typescript try { const data = await fetchVaultData(address, chain); return data; } catch (error) { console.error("Failed to fetch vault data:", error); return null; // Fallback to null, don't crash the app } ``` ### 2. Type Safety [#2-type-safety] Full TypeScript coverage ensures correctness: ```typescript export interface VaultReward { token: string; apr: number; tokenAddress: string; } export interface UserClaimableReward { token: string; amount: bigint; source: "merkl"; } ``` ### 3. Caching Strategy [#3-caching-strategy] ```typescript const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes const cache = new Map(); export function getCached( key: string, fetcher: () => Promise ): Promise { const cached = cache.get(key); if (cached && Date.now() - cached.timestamp < CACHE_DURATION) { return Promise.resolve(cached.data); } return fetcher().then((data) => { cache.set(key, { data, timestamp: Date.now() }); return data; }); } ``` ### 4. Simulation Before Execution [#4-simulation-before-execution] ```typescript // File: src/lib/simulation.ts export async function simulateClaimTransaction( distributorAddress: string, txData: string, userAddress: string ) { const publicClient = createPublicClient({ chain: mainnet, transport: http(), }); try { await publicClient.call({ to: distributorAddress, data: txData, from: userAddress, }); return { success: true }; } catch (error) { return { success: false, error }; } } ``` ## Adapting the Demo for Production [#adapting-the-demo-for-production] When building on this demo for production, consider: ### Security [#security] * **Never expose private keys**: Use secure wallet connection libraries (RainbowKit, Wagmi, etc.) * **Validate all inputs**: Sanitize user addresses, vault addresses, chain IDs * **Handle transaction failures**: Implement retry logic with exponential backoff * **Rate limiting**: Respect API rate limits and implement client-side throttling ### Performance [#performance] * **Lazy load components**: Use React.lazy for routes * **Optimize bundle size**: Code split by route and feature * **Implement pagination**: For users with many rewards * **Use a state management solution**: Redux, Zustand, or React Query for complex state ### User Experience [#user-experience] * **Transaction notifications**: Toast notifications for claim status * **Transaction history**: Show past claims * **Gas estimation**: Display estimated gas costs before claiming ### Monitoring [#monitoring] * **Error tracking**: Sentry, Bugsnag, or similar * **Analytics**: Track claim success rates, popular vaults, etc. * **Logging**: Structured logging for debugging ## Next Steps from the Demo [#next-steps-from-the-demo] After exploring the demo: 1. **Fork and modify**: Adapt it to your use case 2. **Add wallet connection**: Integrate RainbowKit or ConnectKit 3. **Customize UI**: Apply your brand's design system 4. **Deploy**: Vercel, Netlify, or your preferred platform ## Resources [#resources] * **Demo Repository**: [github.com/morpho-org/merkl-morpho-recipe](https://github.com/morpho-org/merkl-morpho-recipe) * **Morpho API**: [api.morpho.org/graphql](https://api.morpho.org/graphql) * **Merkl Docs**: [docs.merkl.xyz](https://docs.merkl.xyz/) * **Legacy Rewards Lookup**: [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) **Tutorials** * [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) * [Claim Rewards](/developers/rewards/tutorials/claim-rewards) * [Integrate Display](/developers/rewards/tutorials/integrate-display) --- # Rewards: Developer Resources Source: https://docs.morpho.org/developers/rewards/resources/all This page is your central hub for all the tools, links, and data sources you need to integrate Morpho rewards into your application. ## Quick Reference [#quick-reference] | Resource | Link | | ------------------ | ---------------------------------------------------------------------------------------------- | | **Morpho API** | [api.morpho.org/graphql](https://api.morpho.org/graphql) | | **Merkl API** | [api.merkl.xyz/docs](https://api.merkl.xyz/docs#description/introduction) | | **Legacy Rewards** | [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) | | **Example Code** | [github.com/morpho-org/merkl-morpho-recipe](https://github.com/morpho-org/merkl-morpho-recipe) | | **Merkl Docs** | [docs.merkl.xyz](https://docs.merkl.xyz/) | ## APIs [#apis] ### Morpho API (Recommended for Vault/Market Rewards) [#morpho-api-recommended-for-vaultmarket-rewards] The Morpho API provides integrated rewards data alongside vault and market information. * **GraphQL Playground**: [api.morpho.org/graphql](https://api.morpho.org/graphql) * **Documentation**: [Morpho API Docs](/developers/api/morpho/) * **Rewards Queries**: * [Morpho Markets Rewards](/developers/api/rewards/#morpho-markets-rewards) * [Morpho Vaults Rewards](/developers/api/rewards/#morpho-vaults-rewards) **Use Cases:** * Fetch vault APY + rewards APR * Get market reward rates (supply, borrow, collateral) * Combine base yield and rewards in one query * Historical data and time-series queries ### Merkl API (Primary for User Rewards) [#merkl-api-primary-for-user-rewards] Merkl's REST API for current reward programs and user claimable amounts. * **Base URL**: `https://api.merkl.xyz/` * **Documentation**: [api.merkl.xyz/docs](https://api.merkl.xyz/docs#description/introduction) * **Key Endpoint**: * User Rewards (includes the cumulative `amount`, `claimed`, and merkle `proofs` needed to claim): `GET /v4/users/{address}/rewards?chainId={chainId}` **Use Cases:** * Fetch user claimable rewards (current programs) * Get Merkle proofs for claiming * Real-time reward accrual (updates every 8 hours) ### Legacy Rewards (Deprecated) [#legacy-rewards-deprecated] **Legacy Rewards API Deprecated** The `rewards.morpho.org` API has been deprecated. If you or your users have unclaimed rewards from historical Morpho programs (pre-Merkl migration), they can be claimed directly through the legacy rewards lookup app: 👉 **[rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/)** All new reward programs are distributed exclusively via [Merkl](https://app.merkl.xyz/). See the [Merkl documentation](https://docs.merkl.xyz/) for integration details. ## Smart Contracts [#smart-contracts] ### Merkl Distributor Contracts [#merkl-distributor-contracts] All Merkl smart contract addresses, categorized by chain are listed [here](https://app.merkl.xyz/status). **Key Functions:** * `claim(address[] users, address[] tokens, uint256[] amounts, bytes32[][] proofs)` - Claim cumulative Merkl reward amounts ### Morpho URD Contracts (Legacy) [#morpho-urd-contracts-legacy] These contracts are still active onchain for claiming historical rewards. Use [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) to look up your claimable amounts and proofs. * See [Rewards Addresses](/get-started/resources/addresses/#rewards) for all deployments **Key Functions:** * `claim(address account, address reward, uint256 claimable, bytes32[] proof)` - Claim rewards * `claimed(address account, address reward) view returns (uint256)` - Check claimed amount **Repository:** * GitHub: [morpho-org/universal-rewards-distributor](https://github.com/morpho-org/universal-rewards-distributor) ### Contract ABIs [#contract-abis] **Merkl Distributor ABI:** ```json [ { "inputs": [ { "name": "users", "type": "address[]" }, { "name": "tokens", "type": "address[]" }, { "name": "amounts", "type": "uint256[]" }, { "name": "proofs", "type": "bytes32[][]" } ], "name": "claim", "outputs": [], "stateMutability": "nonpayable", "type": "function" } ] ``` **URD ABI (Claim):** ```json [ { "inputs": [ { "name": "account", "type": "address" }, { "name": "reward", "type": "address" }, { "name": "claimable", "type": "uint256" }, { "name": "proof", "type": "bytes32[]" } ], "name": "claim", "outputs": [{ "name": "amount", "type": "uint256" }], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "name": "account", "type": "address" }, { "name": "reward", "type": "address" } ], "name": "claimed", "outputs": [{ "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" } ] ``` ## Code Examples & Repositories [#code-examples--repositories] * **Merkl + Morpho Recipe**: * GitHub: [morpho-org/merkl-morpho-recipe](https://github.com/morpho-org/merkl-morpho-recipe) * A complete Next.js demo showing: * Vault metrics with rewards * User claimable rewards (Merkl) * Claim implementations * TypeScript best practices ## SDKs & Tools [#sdks--tools] ### Morpho SDKs [#morpho-sdks] While there's no dedicated rewards SDK, you can use Morpho's core SDKs for related functionality: * **`@morpho-org/morpho-sdk`**: The default SDK for vaults and markets - entity classes (`/entities`) and Viem-based fetchers (`/fetch`) via subpath exports * **`@morpho-org/simulation-sdk`**: Simulate transactions **Learn More**: [Morpho SDKs Documentation](/developers/sdks/get-started/) ### GraphQL Clients [#graphql-clients] For querying the Morpho API: * **[graphql-request](https://github.com/jasonkuhrt/graphql-request)**: Lightweight GraphQL client * **[Apollo Client](https://www.apollographql.com/docs/react/)**: Full-featured GraphQL client * **[urql](https://formidable.com/open-source/urql/)**: Flexible GraphQL client ### Web3 Libraries [#web3-libraries] For claiming rewards onchain: * **[viem](https://viem.sh/)**: Modern TypeScript web3 library (recommended) * **[ethers.js](https://docs.ethers.org/)**: Popular web3 library * **[web3.js](https://web3js.readthedocs.io/)**: Classic web3 library ## External Documentation [#external-documentation] ### Merkl [#merkl] * **Official Docs**: [docs.merkl.xyz](https://docs.merkl.xyz/) * **Integration Guide**: [docs.merkl.xyz/integrate-merkl/app](https://docs.merkl.xyz/integrate-merkl/app) ### Morpho [#morpho] * **Developer Hub**: [/developers/](/developers/) * **API Documentation**: [/developers/api/rewards/](/developers/api/rewards/) ## Onchain Data Sources [#onchain-data-sources] * **Merkl Subgraphs**: See [Merkl docs](https://docs.merkl.xyz/) ## Tutorials & Guides [#tutorials--guides] ### Getting Started [#getting-started] * [Rewards Integration Overview](/developers/rewards/get-started) * [Understanding Reward Programs](/developers/rewards/concepts/reward-campaigns) * [Distribution System (Merkl)](/developers/rewards/concepts/distribution-system) ### Step-by-Step Tutorials [#step-by-step-tutorials] * [Create a Rewards Program](/developers/rewards/tutorials/create-program) * [Fetch Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) * [Claim Rewards](/developers/rewards/tutorials/claim-rewards) * [Integrate Rewards Display](/developers/rewards/tutorials/integrate-display) ### Advanced [#advanced] * [Complete Integration Example](/developers/rewards/guides/complete-integration) ## Support Channels [#support-channels] ### For Integrators [#for-integrators] * **Technical Questions**: [help.morpho.org](https://help.morpho.org) ### For Reward Issuers [#for-reward-issuers] * **Program Creation Help**: [Merkl Docs](https://docs.merkl.xyz/) ## Monitoring & Analytics [#monitoring--analytics] ### Dashboards [#dashboards] * **Merkl Dashboard**: [merkl.xyz](https://merkl.xyz/) - View active campaigns * **Morpho App**: [app.morpho.org](https://app.morpho.org) - See rewards on vaults/markets ### Analytics Tools [#analytics-tools] * **Dune Analytics**: [Morpho Dashboards](/developers/ecosystem/data-dashboards/) * **DefiLlama**: Track TVL and yields across Morpho ## Governance & Updates [#governance--updates] ### Morpho Governance [#morpho-governance] * **Snapshot**: [snapshot.box/#/s:morpho.eth](https://snapshot.box/#/s\:morpho.eth) ### Migration Information [#migration-information] * **MIP 111**: [Snapshot Proposal](https://snapshot.box/#/s\:morpho.eth/proposal/0xadf3862d97cf4e8bb84a4b3d5117abe1a9db774e92ad97c65bde22214c0b76c9) - Migration to Merkl ## Need Help? [#need-help] If you can't find what you're looking for, reach out via: * **Chat**: [help.morpho.org](https://help.morpho.org) --- # Claim Rewards Source: https://docs.morpho.org/developers/rewards/tutorials/claim-rewards This tutorial shows you how to implement reward claiming for **Merkl** programs in your application. ## Overview [#overview] Claiming rewards involves: 1. Fetching claim data (proof + transaction data) from the appropriate API 2. Constructing or using pre-formatted transaction calldata 3. Submitting the claim transaction onchain 4. Handling the response and updating your UI ## Prerequisites [#prerequisites] * Understanding of [Distribution Systems](/developers/rewards/concepts/distribution-system) * Familiarity with [Fetching Rewards Data](/developers/rewards/tutorials/fetch-rewards-data) * A web3 library (viem, ethers, web3.js) * User's wallet connected to your app ## Claiming via Merkl [#claiming-via-merkl] Merkl is the primary distribution system for current reward programs. ### Step 1: Fetch Claim Data [#step-1-fetch-claim-data] **Endpoint:** ``` GET https://api.merkl.xyz/v4/users/{address}/rewards?chainId={chainId} ``` The `{address}` must be lowercase, and `chainId` is required. The same endpoint returns everything you need to claim: the token, the cumulative `amount`, the already-`claimed` amount, and the merkle `proofs`. **Example:** ```typescript async function fetchMerklRewards( userAddress: string, chainId: number ): Promise { const response = await fetch( `https://api.merkl.xyz/v4/users/${userAddress.toLowerCase()}/rewards?chainId=${chainId}` ); if (!response.ok) { throw new Error(`Merkl API error: ${response.status}`); } // The endpoint returns an array of per-chain objects; flatten to a flat list of rewards. const data: MerklChainRewards[] = await response.json(); return data.flatMap((entry) => entry.rewards); } ``` **Response Structure:** ```json [ { "chain": { "id": 8453, "name": "Base" }, "rewards": [ { "token": { "address": "0xTOKEN", "symbol": "MORPHO", "decimals": 18 }, "amount": "2000000000000000000", "claimed": "1000000000000000000", "pending": "0", "proofs": ["0xproof1a", "0xproof1b"] } ] } ] ``` `amount` is the cumulative total earned to date and `claimed` is the cumulative amount already claimed onchain — both are passed as-is. The distributor transfers the difference (`amount - claimed`), so you do **not** subtract them yourself. `pending` is reward that is not yet in a live merkle root and cannot be claimed. **Validate claim data before signing.** The `tokens`, `amounts`, and `proofs` come from an external API and are passed straight into an onchain `claim` that moves funds. Treat the response as untrusted: confirm the token addresses are ones you expect and the amounts are non-zero before prompting the user to sign. ### Step 2: Execute the Claim [#step-2-execute-the-claim] Using **viem**: ```typescript import { createWalletClient, custom } from "viem"; import { mainnet } from "viem/chains"; // Same Distributor address across all supported chains const MERKL_DISTRIBUTOR = "0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae"; async function claimMerklRewards( userAddress: string, chainId: number ) { // 1. Fetch the user's rewards const rewards = await fetchMerklRewards(userAddress, chainId); // Keep only genuinely-claimable rewards — entries with no proofs or nothing // left to claim make the whole `claim` tx revert with `InvalidProof`. const claimable = rewards.filter( (r) => r.proofs.length > 0 && BigInt(r.amount) > BigInt(r.claimed) ); // Build the parallel, index-aligned arrays the contract expects const users = claimable.map(() => userAddress); const tokens = claimable.map((r) => r.token.address); const amounts = claimable.map((r) => BigInt(r.amount)); // cumulative; pass as-is const proofs = claimable.map((r) => r.proofs); // 2. Setup wallet client // The claim must be sent from the user's own wallet — a third-party sender // reverts with `NotWhitelisted` unless it has been approved as an operator. const walletClient = createWalletClient({ account: userAddress, chain: mainnet, transport: custom(window.ethereum), }); // 3. Send claim transaction const hash = await walletClient.writeContract({ address: MERKL_DISTRIBUTOR, abi: MERKL_ABI, functionName: "claim", args: [users, tokens, amounts, proofs], }); // 4. Wait for confirmation const receipt = await walletClient.waitForTransactionReceipt({ hash }); return receipt; } // Merkl Distributor ABI (claim function) const MERKL_ABI = [ { inputs: [ { name: "users", type: "address[]" }, { name: "tokens", type: "address[]" }, { name: "amounts", type: "uint256[]" }, { name: "proofs", type: "bytes32[][]" }, ], name: "claim", outputs: [], stateMutability: "nonpayable", type: "function", }, ] as const; ``` ### Step 3: Handle the Result [#step-3-handle-the-result] ```typescript try { const receipt = await claimMerklRewards(userAddress, 1); if (receipt.status === "success") { console.log("✅ Merkl rewards claimed successfully!"); // Update your UI to reflect claimed rewards } else { console.error("❌ Claim transaction failed"); } } catch (error) { console.error("Error claiming Merkl rewards:", error); // Show error message to user } ``` For more details on Merkl claiming, see the [official Merkl documentation](https://docs.merkl.xyz/integrate-merkl/app#claiming-user-rewards). ## Legacy Rewards (Pre-Merkl) [#legacy-rewards-pre-merkl] **Legacy Rewards API Deprecated** The `rewards.morpho.org` API has been deprecated. If you or your users have unclaimed rewards from historical Morpho programs (pre-Merkl migration), they can be claimed directly through the legacy rewards lookup app: 👉 **[rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/)** All new reward programs are distributed exclusively via [Merkl](https://app.merkl.xyz/). See the [Merkl documentation](https://docs.merkl.xyz/) for integration details. ## Resources [#resources] * **Merkl Claim Docs**: [docs.merkl.xyz/integrate-merkl/app#claiming-user-rewards](https://docs.merkl.xyz/integrate-merkl/app#claiming-user-rewards) * **Legacy Rewards Lookup**: [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) * **Example Code**: [morpho-org/merkl-morpho-recipe](https://github.com/morpho-org/merkl-morpho-recipe) --- # Create a Rewards Program Source: https://docs.morpho.org/developers/rewards/tutorials/create-program This tutorial guides you through creating a new rewards program for Morpho Vaults or Markets using **Merkl**, the current standard for reward distribution. **Target Audience**\ This tutorial is for: * Protocol teams looking to incentivize specific Morpho Markets or Vaults * DAOs planning reward campaigns * Token issuers wanting to boost liquidity in Morpho * Projects building on top of Morpho ## Prerequisites [#prerequisites] Before creating a rewards program, ensure you have: * [ ] A clear incentive strategy (what behavior do you want to encourage?) * [ ] Reward tokens ready for distribution * [ ] Knowledge of target Morpho Markets or Vaults * [ ] A wallet with assets to deposit reward tokens * [ ] Understanding of [Reward Program Types](/developers/rewards/concepts/reward-campaigns) ## Overview: Merkl for Rewards Programs [#overview-merkl-for-rewards-programs] [Merkl](https://merkl.xyz/) is the standard distribution system for Morpho rewards. It provides: * **Automated Distribution**: Rewards calculated and distributed every 8 hours * **Flexible Configuration**: Target specific markets, vaults, or user groups * **Multi-Chain Support**: Works across all chains where Morpho is deployed * **Transparent Tracking**: Real-time visibility into reward distribution ## Creating a Program via Merkl [#creating-a-program-via-merkl] Merkl provides a user-friendly interface for creating reward campaigns. The process differs slightly between Vault and Market programs. ### Jump on the studio [#jump-on-the-studio] [https://studio.merkl.xyz/create-campaign/lend](https://studio.merkl.xyz/create-campaign/lend) ### Select `Morpho` as protocol [#select-morpho-as-protocol]
### Fill the inputs as per Merkl doc [#fill-the-inputs-as-per-merkl-doc]
## Next Steps [#next-steps] * **Fetch campaign data**: Follow the [Fetch Rewards Data tutorial](/developers/rewards/tutorials/fetch-rewards-data) * **Integrate display**: Check out the [Integrate Display tutorial](/developers/rewards/tutorials/integrate-display) * **Enable claiming**: See the [Claim Rewards tutorial](/developers/rewards/tutorials/claim-rewards) --- # Fetch Rewards Data Source: https://docs.morpho.org/developers/rewards/tutorials/fetch-rewards-data This tutorial shows you how to fetch reward information for Morpho Vaults and Markets from both **Merkl API** and **Morpho API**. Understanding how to query both systems is essential for displaying complete reward information to your users. ## What You'll Learn [#what-youll-learn] * How to fetch Vault V2 reward rates from Morpho API * How to query user-specific rewards from Merkl API * How to combine data from multiple sources for a complete picture ## Prerequisites [#prerequisites] * Basic understanding of [Reward Programs](/developers/rewards/concepts/reward-campaigns) * Familiarity with REST and GraphQL APIs * A tool for making API requests (curl, fetch, axios, or GraphQL client) ## API Landscape Overview [#api-landscape-overview] There are **two main APIs** for rewards data: | API | Use Case | Rewards Type | Data Provided | | ------------------ | ------------------------- | ------------ | ------------------------------------------------------------------------------- | | **Morpho API** | Vault/Market reward rates | Merkl only | APRs, reward token info, integrated with vault/market data | | **Merkl API** | User claimable rewards | Merkl only | User balances, claim data, real-time rewards | | **Legacy Rewards** | User claimable rewards | URD only | Deprecated. Use [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) | **Recommended Strategy:** 1. Use **Morpho API** for displaying reward APRs on vaults/markets 2. Use **Merkl API** for user-specific Merkl rewards 3. For legacy URD rewards, direct users to [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) ## Fetching Morpho Vault V2 Rewards [#fetching-morpho-vault-v2-rewards] ### Using Morpho API [#using-morpho-api] Morpho Vault V2 rewards are **pre-aggregated** by the API. The `rewards` field on a Morpho Vault V2 already includes all reward sources across its supported adapters. ```graphql query VaultV2Rewards($address: String!, $chainId: Int!) { vaultV2ByAddress(address: $address, chainId: $chainId) { address name symbol apy netApy # Complete APY including all rewards # Pre-aggregated rewards from all sources rewards { supplyApr asset { address symbol price { usd } } } # Adapters show where the vault allocates adapters { items { type # Adapter type (liquidity, market, and other supported adapters) address assetsUsd } } } } ``` **Variables example:** ```json { "address": "0xb576765fB15505433aF24FEe2c0325895C559FB2", "chainId": 1 } ``` **Returned fields:** Fields below are returned as decimals (e.g. `0.05` = 5%). * `apy`: Current APY of the vault (before fees), derived from liquidity adapter rates. * `netApy`: Current net APY of the vault (after fees, including rewards), derived from liquidity adapter rates. * `rewards[].supplyApr`: APR for each reward token. **Key Integration Notes:** * Use `vaultV2ByAddress` for Vault V2 reward queries * APY and rewards fields are at the **top level** of the response * **No manual aggregation needed**: the API pre-aggregates rewards from supported adapters ## Fetching Market Rewards [#fetching-market-rewards] Markets can have rewards for **supply** and **borrow**. ### Using Morpho API [#using-morpho-api-1] ```graphql query MarketRewards($marketId: String!, $chainId: Int!) { marketById(marketId: $marketId, chainId: $chainId) { marketId loanAsset { symbol } collateralAsset { symbol } state { borrowApy supplyApy rewards { supplyApr borrowApr asset { address symbol price { usd } } } } } } ``` **Key Fields:** * `supplyApr`: Reward APR for suppliers * `borrowApr`: Reward APR for borrowers (often negative, meaning borrowers earn rewards) ## Fetching User-Specific Rewards [#fetching-user-specific-rewards] ### Merkl API (Current Programs) [#merkl-api-current-programs] To get a user's claimable Merkl rewards: **Endpoint:** ``` GET https://api.merkl.xyz/v4/users/{address}/rewards?chainId={chainId} ``` **Example:** ```bash curl "https://api.merkl.xyz/v4/users/0x1234...5678/rewards?chainId=1" ``` **Response Structure:** ```json [ { "chain": { "id": 1, "name": "Ethereum" }, "rewards": [ { "token": { "address": "0xREWARD_TOKEN", "symbol": "MORPHO", "decimals": 18 }, "amount": "2000000000000000000", "claimed": "1000000000000000000", "pending": "0", "proofs": ["0xproof1", "0xproof2"] } ] } ] ``` The response is an array of per-chain objects, each with a `rewards` array. A token's currently-claimable balance is `amount - claimed` (both are cumulative). `pending` is reward not yet in a live merkle root. **TypeScript Example:** ```typescript async function fetchMerklRewards(userAddress: string, chainId: number) { const response = await fetch( `https://api.merkl.xyz/v4/users/${userAddress.toLowerCase()}/rewards?chainId=${chainId}` ); const data = await response.json(); // The endpoint returns an array of per-chain objects; flatten to a list of rewards. const rewards = data.flatMap((entry) => entry.rewards); // Aggregate the unclaimed balance per token (amount - claimed) const claimableByToken: Record = {}; for (const reward of rewards) { const unclaimed = BigInt(reward.amount) - BigInt(reward.claimed); claimableByToken[reward.token.address] = (claimableByToken[reward.token.address] || 0n) + unclaimed; } return claimableByToken; } ``` ### Legacy Rewards (Pre-Merkl) [#legacy-rewards-pre-merkl] **Legacy Rewards API Deprecated** The `rewards.morpho.org` API has been deprecated. If you or your users have unclaimed rewards from historical Morpho programs (pre-Merkl migration), they can be claimed directly through the legacy rewards lookup app: 👉 **[rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/)** All new reward programs are distributed exclusively via [Merkl](https://app.merkl.xyz/). See the [Merkl documentation](https://docs.merkl.xyz/) for integration details. ## Performance Considerations [#performance-considerations] ### Caching [#caching] **Vault/Market Rewards (Morpho API):** * Cache for 5-15 minutes * Rewards rates change infrequently **User Merkl Rewards:** * Cache for 5-10 minutes * Updates every 8 hours, but users expect fresh data ### Parallel Fetching [#parallel-fetching] Always fetch from multiple APIs in parallel: ```typescript const [vaultData, merklRewards] = await Promise.all([ fetchVaultFromMorphoAPI(vaultAddress), fetchMerklRewards(userAddress), ]); ``` ### Error Handling [#error-handling] ```typescript async function fetchRewardsSafely(userAddress: string) { try { const merkl = await fetchMerklRewards(userAddress); return merkl; } catch (error) { console.error("Merkl API error:", error); return {}; // Fallback to empty } } ``` ## API Rate Limits [#api-rate-limits] Be mindful of rate limits: | API | Rate Limit | Notes | | ---------- | ----------------------------------------------------------------- | --------------- | | Morpho API | 5k / 5 min | Use responsibly | | Merkl API | [10 request / second](https://docs.merkl.xyz/integrate-merkl/app) | Cache responses | **Best Practice:** Cache aggressively and fetch only when needed. ## Next Steps [#next-steps] * **Display rewards**: Follow the [Integrate Display tutorial](/developers/rewards/tutorials/integrate-display) * **Enable claiming**: See the [Claim Rewards tutorial](/developers/rewards/tutorials/claim-rewards) * **Full example**: Check out the [Complete Integration Guide](/developers/rewards/guides/complete-integration) ## Resources [#resources] * **Morpho API Playground**: [api.morpho.org/graphql](https://api.morpho.org/graphql) * **Merkl Docs**: [docs.merkl.xyz/integrate-merkl/app](https://docs.merkl.xyz/integrate-merkl/app) * **Legacy Rewards Lookup**: [rewards-legacy.morpho.org](https://rewards-legacy.morpho.org/) * **Example Code**: [morpho-org/merkl-morpho-recipe](https://github.com/morpho-org/merkl-morpho-recipe) --- # Integrate Rewards Display Source: https://docs.morpho.org/developers/rewards/tutorials/integrate-display This tutorial shows you how to build user interfaces that display reward information from Merkl. A well-designed rewards display helps users understand their earnings and encourages engagement with your platform. ## What to Display [#what-to-display] A complete rewards integration should show: 1. **Vault/Market Level Rewards** - The APR users can earn 2. **User Claimable Rewards** - How much users have earned and can claim 3. **Combined APY/APR** - Total yield including base + rewards 4. **Reward Token Information** - What tokens are being distributed 5. **Claim Status** - What's claimable now vs. pending ## Displaying Vault Rewards [#displaying-vault-rewards] ### Fetching and Calculating Total APY [#fetching-and-calculating-total-apy] ```typescript import { GraphQLClient, gql } from "graphql-request"; const client = new GraphQLClient("https://api.morpho.org/graphql"); async function getVaultWithRewards(vaultAddress: string, chainId: number) { const query = gql` query VaultRewards($address: String!, $chainId: Int!) { vaultByAddress(address: $address, chainId: $chainId) { address name symbol asset { symbol } state { apy totalAssets allRewards { supplyApr asset { address symbol price { usd } } } } } } `; const response = await client.request(query, { address: vaultAddress, chainId, }); const vault = response.vaultByAddress; return { vault: vault.name, symbol: vault.symbol, asset: vault.asset.symbol, baseApy: parseFloat(vault.state.apy), rewardsApr: vault.state.allRewards.reduce( (sum, r) => sum + parseFloat(r.supplyApr), 0 ), totalApy: parseFloat(vault.state.apy) + vault.state.allRewards.reduce((sum, r) => sum + parseFloat(r.supplyApr), 0), rewards: vault.state.allRewards.map((r) => ({ token: r.asset.symbol, apr: parseFloat(r.supplyApr), tokenAddress: r.asset.address, })), }; } ``` ### React Component Example [#react-component-example] ```tsx import React, { useEffect, useState } from "react"; interface VaultRewardsDisplayProps { vaultAddress: string; chainId: number; } function VaultRewardsDisplay({ vaultAddress, chainId }: VaultRewardsDisplayProps) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { getVaultWithRewards(vaultAddress, chainId) .then(setData) .finally(() => setLoading(false)); }, [vaultAddress, chainId]); if (loading) return
Loading...
; if (!data) return
No data
; return (

{data.vault}

Deposit {data.asset}

{/* Total APY */}

Total APY: {data.totalApy.toFixed(2)}%

Base APY: {data.baseApy.toFixed(2)}%
Rewards APR: +{data.rewardsApr.toFixed(2)}%
{/* Rewards Breakdown */} {data.rewards.length > 0 && (

Reward Programs:

{data.rewards.map((reward) => (
{reward.token} {reward.apr.toFixed(2)}% APR
))}
)}
); } ``` ### Visual Design Tips [#visual-design-tips] **Highlight Total Yield:** ```tsx
Total APY {totalApy.toFixed(2)}%
``` **Show Breakdown on Hover/Click:** ```tsx
Base APY: {baseApy}%
MORPHO Rewards: +{morphoApr}%
Partner Rewards: +{partnerApr}%
}> {totalApy}%
``` **Use Badges for Rewards:** ```tsx {rewards.length > 0 && ( +{rewardsApr.toFixed(1)}% Rewards )} ``` ## Displaying User Claimable Rewards [#displaying-user-claimable-rewards] ### Fetching User Rewards [#fetching-user-rewards] ```typescript async function getUserAllClaimableRewards(userAddress: string, chainId: number) { const merklRewards = await fetchMerklClaimable(userAddress, chainId); return { merkl: merklRewards }; } async function fetchMerklClaimable(userAddress: string, chainId: number) { const response = await fetch( `https://api.merkl.xyz/v4/users/${userAddress.toLowerCase()}/rewards?chainId=${chainId}` ); const data = await response.json(); // The endpoint returns an array of per-chain objects; flatten to a list of rewards. const rewards = data.flatMap((entry) => entry.rewards); const claimable: Array<{ token: string; amount: bigint; symbol: string; decimals: number; }> = []; for (const reward of rewards) { const unclaimed = BigInt(reward.amount) - BigInt(reward.claimed); claimable.push({ token: reward.token.address, amount: unclaimed, symbol: reward.token.symbol, decimals: reward.token.decimals, }); } return claimable; } ``` ### React Component for User Rewards [#react-component-for-user-rewards] ```tsx import { formatUnits } from "viem"; function UserRewardsDisplay({ userAddress, chainId }: { userAddress: string; chainId: number; }) { const [rewards, setRewards] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { getUserAllClaimableRewards(userAddress, chainId) .then(setRewards) .finally(() => setLoading(false)); }, [userAddress, chainId]); if (loading) return
Loading rewards...
; if (!rewards || rewards.merkl.length === 0) { return
No claimable rewards
; } return (

Your Claimable Rewards

{rewards.merkl.map((reward) => (
{reward.symbol} {formatUnits(reward.amount, reward.decimals)} {reward.symbol}
))} {/* Show separate counts for transparency */}
Merkl: {rewards.merkl.length} rewards
{/* Claim button */}
); } ``` ## Displaying Rewards in Tables [#displaying-rewards-in-tables] For portfolio or dashboard views: ```tsx function VaultPortfolio({ userAddress, chainId }: { userAddress: string; chainId: number; }) { const [vaults, setVaults] = useState([]); return ( {vaults.map((vault) => ( ))}
Vault Your Deposit Base APY Rewards APR Total APY Claimable
{vault.name} {vault.userDeposit} {vault.asset.symbol} {vault.baseApy.toFixed(2)}% +{vault.rewardsApr.toFixed(2)}% {vault.totalApy.toFixed(2)}% {vault.claimable > 0 ? ( ) : ( - )}
); } ``` ## Showing Reward Program Details [#showing-reward-program-details] Provide transparency about active programs: ```tsx function RewardProgramsCard({ vaultAddress, chainId }) { const [programs, setPrograms] = useState([]); // Fetch active programs for this vault useEffect(() => { fetchActivePrograms(vaultAddress, chainId).then(setPrograms); }, [vaultAddress, chainId]); return (

Active Reward Programs

{programs.map((program) => (
{program.token} {program.token} Rewards
APR: {program.apr.toFixed(2)}%
Ends: {formatDate(program.endDate)}
Source: Merkl
))}
); } ``` ## Real-Time Updates [#real-time-updates] ### Polling for Updates [#polling-for-updates] ```typescript function useRewardsPolling(userAddress: string, chainId: number) { const [rewards, setRewards] = useState(null); useEffect(() => { const fetchRewards = () => { getUserAllClaimableRewards(userAddress, chainId).then(setRewards); }; // Initial fetch fetchRewards(); // Poll every 5 minutes for Merkl updates const interval = setInterval(fetchRewards, 5 * 60 * 1000); return () => clearInterval(interval); }, [userAddress, chainId]); return rewards; } ``` ### Showing Accruing Rewards [#showing-accruing-rewards] For a more dynamic experience: ```tsx function AccruingRewardsDisplay({ baseAmount, ratePerSecond }) { const [accrued, setAccrued] = useState(baseAmount); useEffect(() => { const interval = setInterval(() => { setAccrued((prev) => prev + ratePerSecond); }, 1000); return () => clearInterval(interval); }, [ratePerSecond]); return (
Accruing: {accrued.toFixed(6)} MORPHO
); } ``` ## Handling Edge Cases [#handling-edge-cases] ### No Rewards Available [#no-rewards-available] ```tsx {rewards.length === 0 ? (

No active reward programs for this vault

Check back later or explore other vaults

) : ( )} ``` ### Failed to Fetch Rewards [#failed-to-fetch-rewards] ```tsx {error ? (

Unable to load rewards data

) : ( )} ``` ### Dust Amounts [#dust-amounts] Don't show tiny amounts that aren't worth claiming: ```typescript const DUST_THRESHOLD = 0.01; // $0.01 USD function filterDust(rewards: any[], tokenPrices: Record) { return rewards.filter((reward) => { const usdValue = parseFloat(formatUnits(reward.amount, 18)) * tokenPrices[reward.token]; return usdValue > DUST_THRESHOLD; }); } ``` ## Accessibility Considerations [#accessibility-considerations] * **Use semantic HTML**: `` for tabular data, `