# Get DeFi protocol integrations Source: https://docs.mellow.finance/api-reference/get-defi-protocols GET /v1/defi/protocols Retrieves information about all DeFi protocol integrations and their points distribution across different chains. # Get user's vaults DeFi positions Source: https://docs.mellow.finance/api-reference/get-user-defi-positions GET /v1/defi/users/{user_address} Retrieves detailed information about a user's positions across all integrated DeFi protocols, including points earned, balances, and protocol-specific details. # Get user's vaults positions Source: https://docs.mellow.finance/api-reference/get-user-positions GET /v1/users/{user_address} Retrieves detailed information about a specific user's positions across all Mellow Protocol vaults. This includes their liquidity provisions, earned profits, and current holdings as a Liquidity Provider. # Get all vaults Source: https://docs.mellow.finance/api-reference/get-vaults GET /v1/vaults Retrieves comprehensive information about all available vaults in the Mellow Protocol ecosystem. Each vault represents a smart contract that manages liquidity across different DeFi protocols. The response includes vault configurations, associated tokens, current performance metrics, and operational parameters. # Core Vaults and Other Approaches Source: https://docs.mellow.finance/core-vaults-and-other-approaches How Core Vaults compare to ERC-4626, ERC-7540, and common vault architectural patterns – evaluated against institutional requirements. ## Core Vaults and other approaches Mellow provides vault infrastructure for launching onchain Earn, treasury, and managed yield products. Distribution partners can use Core Vaults to launch these products under their own brand, without constructing the management layer from scratch. ERC-4626 and ERC-7540 are not competitors to Core Vaults – they define vault interfaces. Core Vaults operate at the management layer: strategy execution, permissions, accounting, compliance, and multi-venue control. This page compares both interface standards and broader architectural patterns against the requirements institutional capital introduces. *** ### Institutional requirements Basic vault interfaces cover deposits, share issuance, and redemptions. Institutional vault infrastructure requires additional controls for settlement timing, permissions, accounting, compliance, NAV, and multi-venue execution. **Settlement flexibility.** Not all strategies settle atomically. Some strategies have unbonding periods. Tokenized funds and private credit may redeem on fixed schedules. CEX positions settle offchain. A vault managing capital across these sources needs synchronous and asynchronous flows, often within the same product. **Strategy isolation.** Strategy isolation limits the impact of failures or misconfiguration in a single allocation. Conservative lending and higher-risk strategies need independent execution contexts with independent risk parameters. **Operation-level risk controls.** Risk policies described only in documentation depend on process. Risk policies enforced at the contract level become execution rules – scoped per strategy, per asset, per operator, and per action. **Multi-venue execution.** A delta-neutral strategy or cross-venue carry trade needs DeFi protocol access and CEX execution under one permission model, one NAV framework, and one vault-level control surface. **Time to new venues.** Yield opportunities move quickly. When a new protocol or market becomes attractive, the speed at which a vault can safely add it determines whether the curator captures the opportunity or misses it. Architectures that require custom code per integration turn every new venue into an engineering and audit cycle. **NAV for externalized capital.** When capital is deployed to a CEX, held in tokenized treasuries, or locked in an unbonding period, onchain balances do not reflect reality. NAV must be oracle-defined. **Compliance at the vault level.** Jurisdiction-aware mandates, allowlisted depositors, transfer-restricted shares, and per-issuer eligibility constraints should be enforced through the vault permission system, not handled only at the distribution layer. **Agent-bounded execution.** Autonomous agents can operate under the same guardrails as human curators, with permissions enforced by verifiers rather than manual oversight. *** ### How common patterns address these requirements **Interface standards: ERC-4626 and ERC-7540.** ERC-4626 standardized synchronous deposit-and-share operations. ERC-7540 added async requests. Both address the interface layer – neither addresses strategy isolation, operation-level permissions, multi-asset strategy accounting, oracle-defined NAV, or compliance. These are outside the standards' scope. Core Vaults operate at that management layer, using ERC-4626 and ERC-7540 as interfaces rather than as the system itself. **Adapter-based architectures.** Protocol-specific adapters handle deposits, withdrawals, and accounting per integration. Each adapter adds audit surface, maintenance load, and integration-specific failure modes. Some adapter-based systems add distribution layers or strategy composition on top, but the core integration model remains per-protocol code. Core Vaults replace per-protocol code with verifier configuration, so the audit surface does not grow with each new venue. **MPC-based architectures.** Capital routes through institutional custody for offchain execution. The vault has limited ability to enforce onchain constraints while capital is outside the contract. NAV depends on offchain reporting. DeFi composability is limited. This pattern fits pure CeFi execution where onchain composability is not a requirement. Core Vaults instead keep DeFi and CeFi under one onchain permission model, with CeFi accessed through off-exchange settlement rather than routed entirely off-chain. **Single-protocol architectures.** Vaults designed around one protocol offer tighter integration and smaller audit surface. The curator's allocation must fit within that protocol's supported markets – cross-venue strategies are architecturally constrained, and the vault's roadmap, risk profile, and economics are coupled to a single protocol's governance and development decisions. Core Vaults are multi-protocol by design, so a curator can diversify across venues and is not bound to one protocol's roadmap. *** ### How Core Vaults address these requirements | Requirement | Common gap | Core Vaults | | - | - | - | | Settlement flexibility | Sync-only or async-only, no mixed flows | Async queues for deposits and redemptions. Sync and async strategies coexist. Signature queues for pre-approved participants | | Strategy isolation | Capital pools into a single execution context | Each strategy operates in its own subvault with dedicated verifier constraints. Strategy isolation limits cross-strategy spillover | | Operation-level risk | Permissions are external or vault-boundary-only | 60+ onchain permission types scoped by strategy, asset, operator, action. Unauthorized transactions cannot be executed | | Multi-venue execution | DeFi-only or CeFi-only, not both | DeFi under verifier constraints. CeFi via Copper ClearLoop and Ceffu. One permission model across both | | Time to new venues | Each integration is a code and audit cycle | Verifier configuration rather than new vault code. For supported interaction patterns, new venues are added without expanding the core audit surface | | NAV for externalized capital | Share price from onchain balances only | Oracle-defined NAV with three-tier validation – normal, suspicious, rejected – and automatic escalation | | Vault-level compliance | External or at distribution endpoint | Jurisdiction-aware mandates, allowlisted depositors, transfer-restricted shares, and per-issuer eligibility can be enforced at the vault level rather than only at the distribution endpoint | | Agent-bounded execution | No onchain guardrails for autonomous operators | Agents operate under the same verifiers as human curators, with the same permission checks and execution bounds | *** ### The verifier model The way a vault connects to yield sources determines its audit surface, maintenance cost, and how quickly it can support new venues. Traditional adapter-based architectures encode protocol-specific logic for every venue, so each new integration means new code, new audit surface, and new maintenance overhead. Core Vaults separate what the vault can do from what it is allowed to do. Execution is handled by shared vault logic. Permissions are defined by verifiers – declarative rules that specify which operations are allowed, scoped to target contracts, function selectors, asset whitelists, and parameter bounds. Any transaction outside those rules cannot be executed. The result is that integration becomes configuration. For supported interaction patterns, adding a new venue is primarily a mandate and verifier setup task rather than new vault code, so the core audit surface stays constant as venues are added. *** ### Capability matrix | Capability | Core Vaults | ERC-4626 | ERC-7540 | Adapter-based | MPC-based | Single-protocol | | - | - | - | - | - | - | - | | Sync operations | Yes | Yes | Implementation-dependent | Usually yes | Varies | Yes | | Async operations | Yes | No | Yes | Varies | Yes | Varies | | Mixed sync and async | Yes | No | Implementation-dependent | Varies | Varies | Varies | | Strategy isolation | Yes – subvaults | No | No | Varies | No | Varies | | Multi-venue: DeFi and CeFi | Yes | No | No | Usually no | CeFi only | No | | Operation-level permissions | 60+ types | No | No | Varies / external | External | Varies | | Oracle-defined NAV | Vault-native | External | External | External | Offchain | Varies | | Multi-source composition | Yes | No | No | Yes, adapter-dependent | No | Limited | | Agent-bounded execution | Yes | No | No | Not native / external | No | No | | CeFi custody integration | Off-exchange settlement rails | No | No | No | Core model | No | | Vault-level compliance | Vault-level | No | No | Varies | Varies | Varies | | Verifier-based integration | Yes | No | No | No | No | No | | New venue without new code | Yes – configuration | N/A | N/A | No – per-protocol code | No | Limited | | Vendor independence | Yes – multi-protocol by design | N/A | N/A | Varies | No | No | *** ### Choosing the right approach Interface standards and single-purpose architectures each solve a slice of the problem: a synchronous interface, an async primitive, a fixed set of protocol adapters, or an off-chain custody route. Each works until a strategy needs more than that slice – a second venue, a CeFi leg, a compliance constraint, a faster integration path. Core Vaults cover the full surface rather than a slice. Multi-protocol allocation, mixed DeFi and CeFi execution, operation-level risk controls, subvault isolation, jurisdiction-aware compliance, oracle-defined NAV, fast venue integration, and agent-bounded execution operate within one management layer, under one permission model, on one audit surface. A curator does not assemble these from separate systems or migrate when a strategy outgrows its original design. The same infrastructure covers a single-protocol vault on day one and a multi-venue structured product later, without re-architecting. For institutional onchain products that need to hold up across venues, asset types, and regulatory requirements, Core Vaults are the management layer to build on. *** ### Core Vaults at a glance * **Architecture:** Verifier-based – declarative rules, not protocol-specific adapters * **Permissions:** 60+ onchain permission types, operation-level scoping * **Strategy model:** Subvault isolation with independent execution contexts * **DeFi access:** see the [canonical integration list](/mellow-vaults-overview#execution-venues) for supported venues * **CeFi access:** Binance, Bybit, Deribit, OKX, Coinbase, Gate.io, Kraken via off-exchange settlement rails – Copper ClearLoop and Ceffu * **NAV:** Oracle-defined, three-tier validation with automatic escalation * **Compliance:** Jurisdiction-aware mandates, allowlisted depositors, transfer-restricted shares * **Audits:** Sherlock, Nethermind * **Track record:** Vault infrastructure since 2021 * **Product evolution:** Concentrated Liquidity Vaults → MultiVaults → Interop Vaults → Core Vaults # Core Vaults for RWA Allocation Source: https://docs.mellow.finance/core-vaults-for-rwa-allocation Core Vault infrastructure for managing tokenized RWA allocations with per-issuer eligibility, async redemption, oracle-defined NAV, and verifier-enforced controls. ## Core Vaults for RWA Allocation #### Overview Tokenized real-world assets (RWA) are one of the primary asset types for vault-based allocation products built on Mellow. Asset managers, funds, treasuries, and asset issuers can use Core Vaults to hold and manage eligible tokenized instruments – tokenized treasuries, money-market funds, private credit, and commodities – under defined mandates with onchain risk enforcement and compliance controls. Tokenization creates the asset. It does not create the management layer around it. Each tokenized instrument carries its own eligibility, pricing, settlement, and transfer requirements, and a managed product has to respect them throughout the asset lifecycle. The product is defined by the mandate. The infrastructure remains the same. #### Time to market Building an RWA allocation product from scratch requires smart contracts, security audits, integrations with regulated issuers, risk monitoring, and compliance review per jurisdiction. Mellow provides these components as existing, audited infrastructure. The distribution partner and vault manager configure a vault, define the mandate, and launch. This can reduce launch timelines from months of custom development to weeks of configuration and review. #### Key concepts **Mandate** A mandate is the set of rules that defines how a vault may operate. It includes eligible depositors, supported assets and issuers, approved venues, allocation caps, liquidity requirements, verifier rules, and oracle constraints. The mandate is encoded at the contract level and enforced by onchain verifiers. Operations that fall outside the mandate cannot be executed. **Roles** * Depositors provide capital * Distribution partners own the product surface and customer relationship. For RWA allocation they include: * Institutions and asset managers – onchain allocation products with eligibility, oracle-defined NAV, liquidity, and risk controls * Asset issuers – strategy products that make their tokenized instruments more useful * Wallets, exchanges, brokers, custodians – managed RWA and treasury products under their own brand * Vault managers or curators operate the strategy within the mandate * Mellow provides the vault infrastructure * Vault contracts enforce the mandate #### RWA use cases **Allocation vaults** A multi-asset vault that holds tokenized treasuries, tokenized credit, commodity exposure, lending positions, and liquidity buffers under one mandate. The mandate defines eligible assets and issuers, depositors, venues, allocation limits, and liquidity requirements. The vault manager operates inside that mandate. The vault enforces the boundary. Per-issuer eligibility, asynchronous settlement, transfer-restriction handling, and oracle-defined NAV apply across positions with different pricing sources and settlement timings. Tokenized treasuries held in these vaults can also serve as the conservative base layer of a stablecoin treasury product; see[ Core Vaults for Stablecoins](https://docs.mellow.finance/core-vaults-for-stablecoins) for that use case. **Asset-issuer vaults** Asset issuers can use vaults to make tokenized instruments more useful across treasury, collateral, reinvestment, and allocation products. A stablecoin issuer can build treasury yield products. A tokenized treasury issuer can build reinvestment vaults. A credit issuer can build allocation vaults around its origination flow. Vault infrastructure becomes part of how an asset competes on utility, not only on trust and liquidity. #### Vault architecture for RWA allocation RWA allocation uses the full Core Vault feature set – multi-asset accounting, asynchronous liquidity, asset eligibility, and oracle-defined NAV – rather than a simple ERC-4626 interface. **Strategy allocation** The mandate defines the approved asset and strategy set. The following are example categories. Actual availability depends on the vault mandate, integrations, jurisdiction, and asset eligibility. * Tokenized treasuries and money-market funds – BlackRock's BUIDL, Ondo's USDY, Superstate's USTB, Franklin Templeton's BENJI, Ondo's OUSG, Circle's USYC. Conservative base layer. Redemption timing varies by issuer (T+0 to T+2); the vault handles async settlement natively. * Tokenized credit – tokenized private credit and credit funds such as ACRED and ACRDX. Longer redemption windows and offchain administration; the vault processes redemptions through queues and tracks withdrawable separately from economic NAV. * Commodity-backed tokens – gold-backed instruments such as PAXG and XAUT. Largely permissionless; these exercise multi-asset accounting and oracle-defined NAV rather than eligibility controls. * Lending markets – Aave, Morpho, Compound, and other onchain lending protocols, where RWA positions can also serve as collateral. The vault enforces approved markets and allocation caps. * CeFi venues – via Copper ClearLoop and Ceffu, under the same permission model as onchain allocations. A single vault can hold positions across all categories simultaneously. Multi-asset accounting and oracle-defined NAV produce a coherent portfolio view across assets with different pricing sources, settlement timings, and liquidity profiles. Adding a new approved asset or venue is typically configuration rather than new contract code. Beyond being held, tokenized RWAs can serve as allocation inputs – collateral, liquidity buffers, or yield legs – within a broader mandate. **Risk enforcement and verifiers** Core Vaults enforce 60+ onchain permissions at the contract level: * Allocation caps per strategy, per asset, and per venue * Asset whitelists – the vault only holds approved assets * Venue whitelists – the vault only routes to approved targets * Oracle price checks before operations execute * Withdrawal queue controls for fair redemption ordering Verifiers are the onchain components that validate each action against the vault's permission set. The verifier model is composable – routine operations may require a single verifier, while high-impact operations can require multiple verifier approvals, time locks, or enhanced validation. Verifiers do not distinguish between human curators and autonomous agents. The same rules apply. Guardrails are configurable through structured governance but cannot be breached during execution. **Compliance** Regulation is moving into the asset and the mandate. The token enforces who can hold it. The mandate enforces what can be done with it. The vault enforces who can operate it. Compliance rules are applied at the vault level, so different products can enforce different eligibility and transfer requirements without changing the base vault architecture. Core Vaults treat mandates as configurable objects. * Share-token eligibility – configurable hooks for KYC and KYB providers, transfer agents, accreditation registries, and jurisdiction-based restrictions * Asset eligibility – per-asset and per-issuer rules; regulated tokenized instruments with transfer restrictions are handled separately from permissionless positions Two consequences for RWA allocation: the vault itself must be an eligible holder of each instrument it allocates to, and the vault's own share token may need downstream transfer restrictions so that vault exposure stays inside the same eligibility perimeter as the underlying. Different products on the same platform enforce different compliance perimeters without separate codebases. #### Agent-operated RWA vaults RWA allocation suits agent operation: continuous, mandate-bounded decisions across instruments with different settlement and liquidity profiles. The same verifier model that bounds a human vault manager bounds an agent – approved assets, venues, allocation bands, and NAV constraints are enforced before execution, and operations outside the mandate cannot be executed. An agent can rebalance and manage liquidity at frequencies impractical for human operators, while the vault ensures it never exceeds its mandate. #### Related pages * [Core Vaults for Stablecoins](https://docs.mellow.finance/core-vaults-for-stablecoins) – stablecoin yield and treasury vaults * [Vault Infrastructure for Fintech Earn Products](https://docs.mellow.finance/vault-infrastructure-for-fintech-earn-products) – architecture overview and full use case reference # Core Vaults for Stablecoins Source: https://docs.mellow.finance/core-vaults-for-stablecoins How fintechs, payment platforms, and institutional treasuries use Mellow vault infrastructure to manage stablecoin balances – multi-strategy allocations, full treasury mandates, and Earn products. ### Overview Stablecoins are one of the primary asset types for vault-based yield and treasury products on Mellow. Companies holding stablecoin balances – payment processors, fintechs, exchanges, banks, neobanks, stablecoin issuers, marketplaces, trading platforms – can use Core Vaults to manage that capital under defined mandates with onchain risk enforcement and compliance controls. The same vault architecture supports multiple stablecoin use cases, from narrow yield mandates to full treasury management. The product is defined by the mandate. The infrastructure remains the same. ### Time to market Building a stablecoin vault product from scratch requires smart contracts, security audits, DeFi integrations, risk monitoring, and compliance review per jurisdiction. Mellow provides these components as existing, audited infrastructure. The distribution partner and vault manager configure a vault, define the mandate, and launch. This can reduce launch timelines from months of custom development to weeks of configuration and review. ### Key concepts **Mandate** A mandate is the set of rules that defines how a vault may operate. It includes eligible depositors, supported assets, approved venues, allocation caps, liquidity requirements, verifier rules, and oracle constraints. The mandate is encoded at the contract level and enforced by onchain verifiers. Operations that fall outside the mandate cannot be executed. **Roles** * **​Depositors** provide stablecoin capital * **​Distribution partners** – wallets, exchanges, fintechs, banks, brokers, stablecoin platforms – own the product surface and customer relationship * **​Curators or vault managers** operate the strategy within the mandate * **​Mellow** provides the vault infrastructure * ​**Vault contracts** enforce the mandate ​ ### Stablecoin use cases #### **Earn products** Earn products on Mellow exist across multiple asset types. This section covers stablecoin Earn specifically. A distribution partner – wallet, exchange, fintech, bank, neobank, broker, asset issuer, or stablecoin platform – offers yield on stablecoin balances under their own brand. The user deposits an eligible stablecoin, sees an APY, and can withdraw according to the product's redemption rules.​ In the stablecoin context, an Earn product is a vault with a yield-focused mandate: approved strategies, risk limits, compliance constraints, and depositor eligibility configured for the partner's product requirements. The distribution partner owns the customer and the brand. The vault manager operates the strategy. Mellow provides the vault infrastructure. The examples below describe possible product profiles. Actual availability depends on the vault mandate, integrations, jurisdiction, and asset eligibility. | Configuration | Strategy set | Risk profile | Typical partner | | - | - | - | - | | **Conservative** | Tokenized T-bills and money-market funds only | Low yield, low risk | Regulated platforms needing defensible underlying assets | | **Balanced** | Tokenized treasuries + onchain lending markets | Moderate yield, transparent risk parameters, vault-enforced allocation limits | Fintechs and exchanges with institutional user base | | **Amplified** | Lending, stablecoin AMMs, basis opportunities, CeFi venues via institutional custody integrations | Higher return target, higher operational and market risk | Platforms with higher risk tolerance and active strategy management | | **Ecosystem** | Chain-specific or protocol-specific native venues | Yield from ecosystem incentives + native sources | L1/L2 networks, DeFi protocols, RWA and stablecoin issuers | #### **Treasury management** Treasury management applies to any holder of stablecoin balances – whether a company managing its balance sheet or an individual managing personal holdings. In both cases, the vault serves the holder's own capital, not an end-user yield product. A Core Vault defines the full operating mandate: liquidity reserve ratios, approved strategies, allocation caps, asset and venue whitelists, and compliance perimeter. The vault manager – or an authorized agent – operates within these constraints. **Companies** Companies using stablecoin settlement – payment processors, marketplaces, fintechs, trading platforms – accumulate balances that need active management: maintain operating reserves, deploy idle capital into approved strategies, enforce risk limits, and satisfy compliance requirements across jurisdictions. **Users** Individuals holding stablecoin balances can manage them under the same vault architecture – a defined mandate with approved strategies, risk limits, and automated enforcement, operated directly or by an authorized agent. ### Vault architecture for stablecoin capital #### **Strategy allocation** The mandate defines the approved strategy set. The following are example strategy categories. Actual availability depends on the vault mandate, integrations, jurisdiction, and asset eligibility. ​ * ​**Tokenized T-bills and money-market funds** – BlackRock's BUIDL, Ondo's USDY, Superstate's USTB, Franklin Templeton's BENJI. Conservative base layer. Redemption timing varies by issuer (T+0 to T+2); the vault handles async settlement natively. * **​Lending markets** – Aave, Morpho, Compound, and other onchain lending protocols. The vault enforces which markets are approved, maximum allocation per market, and utilization thresholds for rebalancing. * ​**Short-duration yield** – liquidity pools, stablecoin AMMs, and other venues with intraday deployment and withdrawal. Serve as both yield generation and liquidity buffer. * **​CEX venues** – via Copper ClearLoop and Ceffu, capital routes to centralized opportunities under the same permission model as DeFi allocations. ​ A single vault can hold positions across all strategy types simultaneously. Multi-asset accounting and oracle-defined NAV produce a coherent portfolio view across assets with different pricing sources, settlement timings, and liquidity profiles. Adding a new approved venue is typically configuration rather than new contract code. #### **Risk enforcement and verifiers** Core Vaults enforce 60+ onchain permissions at the contract level: * Allocation caps per strategy, per asset, and per venue * ​Asset whitelists – the vault only holds approved assets * ​Venue whitelists – the vault only routes to approved targets * ​Oracle price checks before operations execute * ​Withdrawal queue controls for fair redemption ordering Verifiers are the onchain components that validate each action against the vault's permission set. The verifier model is composable – routine operations may require a single verifier, while high-impact operations can require multiple verifier approvals, time locks, or enhanced validation. Verifiers do not distinguish between human curators and autonomous agents. The same rules apply.​ Guardrails are configurable through structured governance but cannot be breached during execution. #### **Compliance** Compliance rules are applied at the vault level, so different products can enforce different eligibility and transfer requirements without changing the base vault architecture. Core Vaults treat mandates as configurable objects. ​Share-token eligibility – configurable hooks for KYC/KYB providers, transfer agents, accreditation registries, jurisdiction-based restrictions Asset eligibility – per-asset and per-issuer rules; regulated tokenized instruments with transfer restrictions are handled separately from permissionless positions Different products on the same platform enforce different compliance perimeters without separate codebases. ### Agent-operated stablecoin vaults As autonomous agents take on more treasury and allocation responsibilities, the infrastructure they operate in matters as much as the models behind them. Autonomous agents need execution environments with hard constraints that prevent unauthorized actions at the contract level. Core Vaults provide this through the verifier model – approved targets, functions, assets, allocation bands, NAV constraints, and time-locked operations are enforced before execution. Operations outside the mandate cannot be executed, without requiring human oversight. Agents operating stablecoin vaults can rebalance, manage liquidity, and react to strategy conditions at frequencies impractical for human operators – while the vault ensures they never exceed their mandate.
### Related pages * [Vault Infrastructure for Fintech Earn Products](https://docs.mellow.finance/vault-infrastructure-for-fintech-earn-products) – architecture overview and full use case reference # Core Vaults Integration Guide Source: https://docs.mellow.finance/core-vaults-integration-guide Reference for integrating deposit and redemption flows with Mellow Core Vaults, including contract ABIs, supported networks, fees, errors, and events, with TypeScript examples. This guide covers the full lifecycle for integrating deposits and redemptions with **Mellow Core Vaults**. ## 1. Architecture Overview A **Mellow Core Vault** is a programmable, modular vault contract. It serves as the central hub for capital management, risk control, and composable logic. **Depositors** provide capital; **Curators** manage that capital within guardrails set by the vault configuration. Async deposit and redemption flows are time-buffered through an offchain oracle – protecting depositors against flash-loan attacks and front-running by design. Sync queues settle in the same transaction at the latest oracle price, within a configured maximum report age. ### Core Components | Component | Role | | - | - | | **Vault** | Central contract. Orchestrates ACLModule (access control), ShareModule (queues & shares), VaultModule (subvault management), and BaseModule (reentrancy). | | **DepositQueue** | Accepts token deposits, stores them as timestamped checkpoints, and mints vault shares after oracle pricing. | | **RedeemQueue** | Accepts share redemptions, locks shares immediately, and releases assets after oracle pricing and liquidity settlement. | | **SyncDepositQueue** / **SyncRedeemQueue** | Settle deposits and redemptions in one transaction at the latest oracle price. No claim step. | | **ShareManager** | ERC20-compatible contract managing share supply, whitelisting, global lockups, and compliance controls. | | **Oracle** | Trusted off-chain price reporter. Submits `handleReport()` with a price and timestamp. | | **Curator** | Manages capital allocation across subvaults; usually calls `handleBatches()` to settle redemption liquidity (the function is permissionless). | ### Deposit Lifecycle #### Async queue Time-buffered. Oracle prices the batch; Curator settles liquidity. Claim is a separate transaction after processing. ``` Step 1 — User calls deposit(assets, referral, merkleProof) └─► Request stored as a timestamped checkpoint in DepositQueue Step 2 — Handle Report submitted handleReport(priceD18, depositTimestamp) └─► Processes all requests older than the configured depositInterval └─► Shares are allocated lazily using a Fenwick tree (computed at claim time) Step 3 — User calls DepositQueue.claim(account) └─► Share amount computed, deposit fee deducted, shares transferred to user ``` #### Sync queue Shares issued or assets returned in the same transaction. No separate claim step. ``` Step 1 — User calls deposit(assets, referral, merkleProof) └─► Share amount computed, deposit fee deducted, shares transferred to user ``` ### Redemption Lifecycle #### Async queue ``` Step 1 — User calls RedeemQueue.redeem(shares) └─► Shares locked immediately from the user's wallet Step 2 — Handle Report submitted handleReport(priceD18, redeemTimestamp) └─► Prices all requests older than the configured redeemInterval Step 3 — Anyone (usually the Curator or a keeper) calls RedeemQueue.handleBatches(n) └─► Pulls required liquidity from vault/subvaults into RedeemQueue └─► RedeemRequestsHandled event emitted; isClaimable becomes true Step 4 — User calls RedeemQueue.claim(receiver, timestamps[]) └─► Underlying assets transferred to receiver ``` #### Sync queue Assets returned in the same transaction. No separate claim step. Limited by vault liquidity and a daily limit. ``` Step 1 — User calls SyncRedeemQueue.redeem(shares, receiver) └─► Shares burned, redeem fee deducted, assets transferred to receiver ``` ## 2. Supported Networks | Chain ID | Network | | - | - | | 1 | Ethereum | | 8453 | Base | | 42161 | Arbitrum | | 17000 | Holesky (testnet) | | 560048 | Hoodi (testnet) | | 143 | Monad (testnet) | | 9745 | Plasma | | 999 | HyperEVM | | 31612 | Mezo | ## 3. Vault Discovery Fetch the list of all vaults from the Mellow REST API. No authentication is required. ``` GET https://api.mellow.finance/v1/vaults → VaultData[] ``` ```typescript theme={null} async function fetchVaults(): Promise { const response = await fetch('https://api.mellow.finance/v1/vaults'); if (!response.ok) { throw new Error(`Failed to fetch vaults: ${response.status} ${response.statusText}`) } return response.json() as Promise } ``` ## 4. TypeScript Interfaces & Constants ```typescript theme={null} import type { Address } from 'viem' // ── Token ───────────────────────────────────────────────────────────────────── interface Token { address: Address symbol: string decimals: number } // ── Queue ───────────────────────────────────────────────────────────────────── interface Queue { /** The queue contract address */ queue: Address /** The token this queue accepts (for deposits) or pays out (for redemptions) */ asset: Address /** When true, new submissions are rejected */ is_paused: boolean /** Async queues require a separate claim step after oracle processing */ type: 'async' | 'sync' } // ── VaultData ───────────────────────────────────────────────────────────────── interface VaultData { id: string chain_id: number address: Address symbol: string /** Decimals used for vault shares — use this when parsing redeem amounts */ decimals: number name: string base_token: Token deposit_tokens: Token[] withdraw_tokens: Token[] collector: Address deposit_queues: Queue[] redeem_queues: Queue[] } // ── RedeemRequest ───────────────────────────────────────────────────────────── interface RedeemRequest { /** uint32 unix timestamp identifying this request */ timestamp: bigint /** Shares submitted for this request */ shares: bigint /** True when the oracle has processed this batch and assets can be claimed */ isClaimable: boolean /** Assets available to claim (0 until isClaimable is true) */ assets: bigint } // ── Constants ───────────────────────────────────────────────────────────────── /** Sentinel address representing native ETH in the Mellow protocol */ const NATIVE_ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' as const /** Maximum value for Solidity uint224 — the deposit() assets parameter type */ const UINT224_MAX = (1n << 224n) - 1n function isNativeEth(address: string): boolean { return address.toLowerCase() === NATIVE_ETH_ADDRESS.toLowerCase() } ``` ## 5. ABIs Only the functions and events relevant to integrations are shown here. ### 5.1 Deposit Queue ABI ```typescript theme={null} const DEPOSIT_QUEUE_ABI = [ // ── View functions ────────────────────────────────────────────────────────── { type: 'function', name: 'asset', inputs: [], outputs: [{ name: '', type: 'address' }], stateMutability: 'view', }, { type: 'function', name: 'requestOf', inputs: [{ name: 'account', type: 'address' }], outputs: [ { name: 'timestamp', type: 'uint256' }, { name: 'assets', type: 'uint256' }, ], stateMutability: 'view', }, { type: 'function', name: 'claimableOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: 'shares', type: 'uint256' }], // returns claimable shares, not assets stateMutability: 'view', }, // ── Write functions ───────────────────────────────────────────────────────── { type: 'function', name: 'deposit', inputs: [ { name: 'assets', type: 'uint224' }, { name: 'referral', type: 'address' }, { name: 'merkleProof', type: 'bytes32[]' }, ], outputs: [], stateMutability: 'payable', }, { type: 'function', name: 'claim', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'bool' }], stateMutability: 'nonpayable', }, { type: 'function', name: 'cancelDepositRequest', inputs: [], outputs: [], stateMutability: 'nonpayable', }, // ── Events ────────────────────────────────────────────────────────────────── { type: 'event', name: 'DepositRequested', inputs: [ { name: 'account', type: 'address', indexed: true }, { name: 'referral', type: 'address', indexed: true }, { name: 'assets', type: 'uint224', indexed: false }, { name: 'timestamp', type: 'uint32', indexed: false }, ], }, { type: 'event', name: 'DepositRequestClaimed', inputs: [ { name: 'account', type: 'address', indexed: true }, { name: 'shares', type: 'uint256', indexed: false }, { name: 'timestamp', type: 'uint32', indexed: false }, ], }, { type: 'event', name: 'DepositRequestCanceled', inputs: [ { name: 'account', type: 'address', indexed: true }, { name: 'assets', type: 'uint256', indexed: false }, { name: 'timestamp', type: 'uint32', indexed: false }, ], }, // ── Errors ────────────────────────────────────────────────────────────────── { type: 'error', name: 'PendingRequestExists', inputs: [] }, { type: 'error', name: 'ClaimableRequestExists', inputs: [] }, { type: 'error', name: 'NoPendingRequest', inputs: [] }, { type: 'error', name: 'QueuePaused', inputs: [] }, { type: 'error', name: 'DepositNotAllowed', inputs: [] }, { type: 'error', name: 'ZeroValue', inputs: [] }, { type: 'error', name: 'ERC20InsufficientBalance', inputs: [ { name: 'sender', type: 'address' }, { name: 'balance', type: 'uint256' }, { name: 'needed', type: 'uint256' }, ], }, ] as const ``` ### 5.2 Redeem Queue ABI ```typescript theme={null} const REDEEM_QUEUE_ABI = [ // ── View functions ────────────────────────────────────────────────────────── { type: 'function', name: 'asset', inputs: [], outputs: [{ name: '', type: 'address' }], stateMutability: 'view', }, { type: 'function', name: 'requestsOf', inputs: [ { name: 'account', type: 'address' }, { name: 'offset', type: 'uint256' }, { name: 'limit', type: 'uint256' }, ], outputs: [ { name: 'requests', type: 'tuple[]', components: [ { name: 'timestamp', type: 'uint256' }, { name: 'shares', type: 'uint256' }, { name: 'isClaimable', type: 'bool' }, { name: 'assets', type: 'uint256' }, ], }, ], stateMutability: 'view', }, // ── Write functions ───────────────────────────────────────────────────────── { type: 'function', name: 'redeem', inputs: [{ name: 'shares', type: 'uint256' }], outputs: [], stateMutability: 'nonpayable', }, { type: 'function', name: 'claim', inputs: [ { name: 'receiver', type: 'address' }, { name: 'timestamps', type: 'uint32[]' }, ], outputs: [{ name: 'assets', type: 'uint256' }], stateMutability: 'nonpayable', }, // ── Events ────────────────────────────────────────────────────────────────── { type: 'event', name: 'RedeemRequested', inputs: [ { name: 'account', type: 'address', indexed: true }, { name: 'shares', type: 'uint256', indexed: false }, { name: 'timestamp', type: 'uint256', indexed: false }, ], }, { type: 'event', name: 'RedeemRequestClaimed', inputs: [ { name: 'account', type: 'address', indexed: true }, { name: 'receiver', type: 'address', indexed: true }, { name: 'assets', type: 'uint256', indexed: false }, { name: 'timestamp', type: 'uint32', indexed: false }, ], }, // ── Errors ────────────────────────────────────────────────────────────────── { type: 'error', name: 'QueuePaused', inputs: [] }, { type: 'error', name: 'ZeroValue', inputs: [] }, { type: 'error', name: 'ERC20InsufficientBalance', inputs: [ { name: 'sender', type: 'address' }, { name: 'balance', type: 'uint256' }, { name: 'needed', type: 'uint256' }, ], }, ] as const ``` #### Sync redeem queue ABI `SyncDepositQueue` uses the same `deposit(assets, referral, merkleProof)` call as `DepositQueue` but emits `Deposited` instead of `DepositRequested`. `SyncRedeemQueue` has its own entry point: ```typescript theme={null} const SYNC_REDEEM_QUEUE_ABI = [ { type: 'function', name: 'redeem', inputs: [ { name: 'shares', type: 'uint256' }, { name: 'receiver', type: 'address' }, ], outputs: [], stateMutability: 'nonpayable', }, { type: 'function', name: 'syncRedeemParams', inputs: [], outputs: [ { name: 'penaltyD6', type: 'uint256' }, { name: 'maxAge', type: 'uint32' }, { name: 'usage', type: 'uint256' }, { name: 'dailyLimit', type: 'uint256' }, { name: 'latestRequestTimestamp', type: 'uint256' }, ], stateMutability: 'view', }, { type: 'function', name: 'remainingDailyLimit', inputs: [], outputs: [ { name: 'usage', type: 'uint256' }, { name: 'remainingDailyLimit', type: 'uint256' }, ], stateMutability: 'view', }, { type: 'function', name: 'getLiquidAssets', inputs: [], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', }, { type: 'event', name: 'Redeemed', inputs: [ { name: 'account', type: 'address', indexed: true }, { name: 'shares', type: 'uint256', indexed: false }, { name: 'assets', type: 'uint256', indexed: false }, { name: 'feeShares', type: 'uint256', indexed: false }, ], }, { type: 'error', name: 'QueuePaused', inputs: [] }, { type: 'error', name: 'ZeroValue', inputs: [] }, { type: 'error', name: 'InvalidReport', inputs: [] }, { type: 'error', name: 'StaleReport', inputs: [] }, { type: 'error', name: 'DailyLimitOverflow', inputs: [] }, { type: 'error', name: 'InsufficientAssets', inputs: [ { name: 'requested', type: 'uint256' }, { name: 'available', type: 'uint256' }, ], }, ] as const ``` ### 5.3 ERC20 ABI (subset – for approval) ```typescript theme={null} const ERC20_ABI = [ { type: 'function', name: 'allowance', inputs: [ { name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }, ], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', }, { type: 'function', name: 'approve', inputs: [ { name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }, ], outputs: [{ name: '', type: 'bool' }], stateMutability: 'nonpayable', }, { type: 'function', name: 'balanceOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', }, ] as const ``` ### 5.4 Vault ABI (subset – for share balance lookup) ```typescript theme={null} const VAULT_ABI = [ { type: 'function', name: 'shareManager', inputs: [], outputs: [{ name: '', type: 'address' }], stateMutability: 'view', }, ] as const ``` The `shareManager` address is itself an ERC20-compatible contract. Use `ERC20_ABI` with `balanceOf`, or use the richer `SHARE_MANAGER_ABI` below for more precise balance reads. ### 5.5 ShareManager ABI (subset) ```typescript theme={null} const SHARE_MANAGER_ABI = [ // ── Balance reads (prefer these over raw balanceOf) ───────────────────────── { type: 'function', name: 'sharesOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', }, { type: 'function', name: 'activeSharesOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', // Returns only the shares that are not locked in a redeem queue. // Use this to check the redeemable balance. }, { type: 'function', name: 'claimableSharesOf', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }], stateMutability: 'view', // Shares processed by the oracle and awaiting DepositQueue.claim(). }, // ── Whitelist / permissions ────────────────────────────────────────────────── { type: 'function', name: 'flags', inputs: [], outputs: [ { name: '', type: 'tuple', components: [ { name: 'hasMintPause', type: 'bool' }, { name: 'hasBurnPause', type: 'bool' }, { name: 'hasTransferPause', type: 'bool' }, { name: 'hasWhitelist', type: 'bool' }, // deposit whitelist active { name: 'hasTransferWhitelist', type: 'bool' }, { name: 'globalLockup', type: 'uint32' }, // seconds all shares are locked after mint ], }, ], stateMutability: 'view', }, { type: 'function', name: 'isDepositorWhitelisted', inputs: [ { name: 'account', type: 'address' }, { name: 'merkleProof', type: 'bytes32[]' }, ], outputs: [{ name: '', type: 'bool' }], stateMutability: 'view', }, ] as const interface ShareManagerFlags { hasMintPause: boolean hasBurnPause: boolean hasTransferPause: boolean hasWhitelist: boolean // when true, deposits require a valid Merkle proof hasTransferWhitelist: boolean globalLockup: number // seconds before newly minted shares become transferable } ``` ## 6. Deposit ### Overview Depositing submits tokens to a `DepositQueue` contract. For **async** queues the shares are not immediately available – an oracle processes the batch and sets a price, after which the user calls `claim` to receive their shares. ``` Step 1 — approve token spend (ERC20 only) Step 2 — call deposit() ↓ [oracle processes batch] ↓ Step 3 — call claim() to receive vault shares ``` ### Steps Pick a deposit queue from `vault.deposit_queues`. You can match by `queue.asset` address and `queue.type` deposit type (`"sync"` | `"async"`). Check `queue.is_paused === false`. Throw early if paused. Read `shareManager.flags()`. If `flags.hasWhitelist === true`, call `shareManager.isDepositorWhitelisted(userAddress, merkleProof)`. If it returns `false`, the deposit will revert with `DepositNotAllowed`. For public vaults (`hasWhitelist === false`), pass `[]` as the proof. Find the matching `Token` in `vault.deposit_tokens` for `queue.asset`. Parse the human-readable amount with `parseUnits(amount, token.decimals)`. Validate that: * `parsedAmount > 0n` * `parsedAmount <= UINT224_MAX` Only one pending deposit request per user is allowed per queue. For async queues, call `requestOf(userAddress)` and check `timestamp === 0n` before depositing. If the asset is native ETH (`queue.asset === NATIVE_ETH_ADDRESS`), check the user's ETH balance, then call `deposit()` with `value = parsedAmount`. If the asset is an ERC20: * Read `allowance(userAddress, queueAddress)`. * If `currentAllowance > 0n`, send `approve(queueAddress, 0n)` first. This is required for tokens like USDT that revert if you set a non-zero allowance on top of an existing one. * Send `approve(queueAddress, parsedAmount)`. * Then call `deposit(parsedAmount, zeroAddress, merkleProof)` with `value = 0n`. For **async** queues: do not expect shares immediately. Poll `claimableOf` or listen for `DepositRequestClaimed` events, then call `claim`. ### Code Example ```typescript theme={null} import { createPublicClient, createWalletClient, http, parseUnits, zeroAddress, } from 'viem' import { mainnet } from 'viem/chains' import { privateKeyToAccount } from 'viem/accounts' const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY') const publicClient = createPublicClient({ chain: mainnet, transport: http() }) const walletClient = createWalletClient({ account, chain: mainnet, transport: http() }) async function deposit( vault: VaultData, queueAddress: string, humanAmount: string, // Pass [] for public vaults. For whitelisted vaults, obtain proof from the Mellow API. merkleProof: `0x${string}`[] = [], ) { const userAddress = account.address // 1. Find the queue and validate it is open const queue = vault.deposit_queues.find(q => q.queue.toLowerCase() === queueAddress.toLowerCase()) if (!queue) throw new Error('Queue not found') if (queue.is_paused) throw new Error('Queue is paused') // 2. Whitelist check — read shareManager.flags() to see if this vault requires a proof const shareManagerAddress = await publicClient.readContract({ address: vault.address, abi: VAULT_ABI, functionName: 'shareManager', }) const flags = await publicClient.readContract({ address: shareManagerAddress, abi: SHARE_MANAGER_ABI, functionName: 'flags', }) as ShareManagerFlags if (flags.hasWhitelist) { const allowed = await publicClient.readContract({ address: shareManagerAddress, abi: SHARE_MANAGER_ABI, functionName: 'isDepositorWhitelisted', args: [userAddress, merkleProof], }) if (!allowed) throw new Error('Address is not whitelisted for this vault') } // 3. Resolve token metadata const token = vault.deposit_tokens.find(t => t.address.toLowerCase() === queue.asset.toLowerCase()) if (!token) throw new Error('Token not found') // 4. Parse and validate amount const parsedAmount = parseUnits(humanAmount, token.decimals) if (parsedAmount <= 0n) throw new Error('Amount must be greater than zero') if (parsedAmount > UINT224_MAX) throw new Error('Amount exceeds maximum (uint224)') const native = isNativeEth(queue.asset) if (native) { // ── Native ETH path ──────────────────────────────────────────────────────── const balance = await publicClient.getBalance({ address: userAddress }) if (parsedAmount > balance) throw new Error('Insufficient ETH balance') const hash = await walletClient.writeContract({ address: queue.queue as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'deposit', args: [parsedAmount, zeroAddress, merkleProof], value: parsedAmount, }) return publicClient.waitForTransactionReceipt({ hash }) } else { // ── ERC20 path ───────────────────────────────────────────────────────────── // Check balance const balance = await publicClient.readContract({ address: queue.asset as `0x${string}`, abi: ERC20_ABI, functionName: 'balanceOf', args: [userAddress], }) if (parsedAmount > balance) throw new Error('Insufficient token balance') // Check for pending request — only one pending request per user per queue is allowed if (queue.type === 'async') { const [timestamp] = await publicClient.readContract({ address: queue.queue as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'requestOf', args: [userAddress], }) if (timestamp > 0n) { throw new Error('Pending deposit request already exists — cancel or claim it first') } } // Read allowance BEFORE sending any transactions const currentAllowance = await publicClient.readContract({ address: queue.asset as `0x${string}`, abi: ERC20_ABI, functionName: 'allowance', args: [userAddress, queue.queue as `0x${string}`], }) // Reset allowance if needed (required for tokens like USDT) if (currentAllowance > 0n) { const resetHash = await walletClient.writeContract({ address: queue.asset as `0x${string}`, abi: ERC20_ABI, functionName: 'approve', args: [queue.queue as `0x${string}`, 0n], }) await publicClient.waitForTransactionReceipt({ hash: resetHash }) } // Approve exact amount const approveHash = await walletClient.writeContract({ address: queue.asset as `0x${string}`, abi: ERC20_ABI, functionName: 'approve', args: [queue.queue as `0x${string}`, parsedAmount], }) await publicClient.waitForTransactionReceipt({ hash: approveHash }) // Deposit const depositHash = await walletClient.writeContract({ address: queue.queue as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'deposit', args: [parsedAmount, zeroAddress, merkleProof], value: 0n, }) return publicClient.waitForTransactionReceipt({ hash: depositHash }) } } ``` **Referral address:** Pass `zeroAddress` (`0x0000000000000000000000000000000000000000`) unless you have been issued a referral address by the Mellow team. **Merkle proof & whitelisting:** Pass `[]` for public vaults. When `shareManager.flags().hasWhitelist === true`, the vault is permissioned - `deposit()` will revert with `DepositNotAllowed` if the proof is invalid or empty. Contact the Mellow team or query the Mellow API to obtain a valid proof for whitelisted addresses. ## 7. Cancel Deposit ### Overview A pending async deposit request can be cancelled before the oracle processes it. Cancelling returns the deposited tokens to the user. You **cannot** cancel once the request is claimable – call `claim` instead. ### Steps Call `requestOf(userAddress)` on the deposit queue. Check `timestamp > 0n` - if zero, there is no pending request. Call `claimableOf(userAddress)`. If `> 0n`, the oracle has already processed the request – you must claim it, not cancel it. Call `cancelDepositRequest()`. This function takes no arguments – it cancels the caller's own request. ### Code Example ```typescript theme={null} async function cancelDeposit(queueAddress: string) { const userAddress = account.address // 1. Check for a pending request const [timestamp] = await publicClient.readContract({ address: queueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'requestOf', args: [userAddress], }) if (timestamp === 0n) throw new Error('No pending deposit request to cancel') // 2. Check whether it is already claimable const claimable = await publicClient.readContract({ address: queueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'claimableOf', args: [userAddress], }) if (claimable > 0n) { throw new Error('Request already processed — call claim() instead of cancel') } // 3. Cancel const hash = await walletClient.writeContract({ address: queueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'cancelDepositRequest', args: [], }) return publicClient.waitForTransactionReceipt({ hash }) } ``` ## 8. Claim Deposit ### Overview After the oracle processes an async deposit request, vault shares are held in the queue contract ready for collection. Call `claim` to transfer them to the user. ### Steps Call `claimableOf(userAddress)`. If `> 0n`, shares are ready to claim. If `claimableOf` returns `0n`, call `requestOf(userAddress)`. If `timestamp > 0n`, the oracle has not yet processed the request – wait and retry later. Call `claim(userAddress)`. Returns `true` when shares are successfully transferred. ### Code Example ```typescript theme={null} async function claimDeposit(queueAddress: string) { const userAddress = account.address // 1. Check if there is anything to claim const claimable = await publicClient.readContract({ address: queueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'claimableOf', args: [userAddress], }) if (claimable === 0n) { // Check if still pending const [timestamp] = await publicClient.readContract({ address: queueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'requestOf', args: [userAddress], }) if (timestamp > 0n) { throw new Error('Deposit is pending oracle processing — try again later') } throw new Error('No claimable deposit found') } // 2. Claim shares const hash = await walletClient.writeContract({ address: queueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, functionName: 'claim', args: [userAddress], }) return publicClient.waitForTransactionReceipt({ hash }) } ``` ## 9. Redeem ### Overview Redemption returns the underlying asset to the user in exchange for vault shares. Check `queue.type` on each redeem queue: **async** queues lock the shares and pay out after oracle processing and a separate claim step, while **sync** queues pay out in the same transaction (see [Sync redeem queues](#sync-redeem-queues) below). ``` Step 1 — call redeem(shares) ↓ [oracle processes batch] ↓ Step 2 — call claim(receiver, timestamps) ``` ### Steps Pick a redeem queue from `vault.redeem_queues`. Check `queue.type`: the steps below are for `"async"` queues. For `"sync"` queues, see [Sync redeem queues](#sync-redeem-queues). Check `queue.is_paused === false`. * Call `vault.shareManager()` to get the share manager address. * Call `shareManager.activeSharesOf(userAddress)` - this returns only shares that are **not** currently locked in a pending redeem request. Use `sharesOf` if you want the total including locked shares. Parse the share amount using **vault decimals** (`vault.decimals`), **not** the token's decimals. This is a common mistake – the share token uses the vault's decimal precision. Validate `parsedShares > 0n` and `parsedShares <= activeShareBalance`. Call `redeem(parsedShares)`. No ETH value, no ERC20 approval – the queue locks shares directly from the caller through the share manager. For async queues, wait for oracle processing and `handleBatches()`, then call `claimRedeem` (see Section 10). **Multiple requests are allowed.** Unlike deposits, a user can have many concurrent redemption requests. Each `redeem()` call creates a new request with its own timestamp. **Requests cannot be cancelled.** Once submitted, a redemption request is permanent. This is intentional – cancellable redemptions would allow yield-griefing (requesting redemption to force liquidity pulls from external protocols, then withdrawing the request). The locked shares are burned when the oracle prices the request. **Settlement flow:** After `redeem()`, two offchain steps must happen before you can claim: (1) the oracle calls `handleReport()` to price the batch, then (2) the Curator or a keeper calls `handleBatches()` on the RedeemQueue to pull the required liquidity from subvaults. Listen for the `RedeemRequestsHandled` event – it fires when `handleBatches()` settles one or more batches and requests become claimable. The timing depends on vault configuration (`redeemInterval`) and curator activity, typically ranging from minutes to hours. ### Code Example ```typescript theme={null} async function redeem(vault: VaultData, queueAddress: string, humanShares: string) { const userAddress = account.address // 1. Find the queue const queue = vault.redeem_queues.find(q => q.queue.toLowerCase() === queueAddress.toLowerCase()) if (!queue) throw new Error('Redeem queue not found') if (queue.is_paused) throw new Error('Redeem queue is paused') // 2. Get redeemable share balance via vault's shareManager // activeSharesOf excludes shares already locked in pending redeem requests const shareManagerAddress = await publicClient.readContract({ address: vault.address, abi: VAULT_ABI, functionName: 'shareManager', }) const activeShareBalance = await publicClient.readContract({ address: shareManagerAddress, abi: SHARE_MANAGER_ABI, functionName: 'activeSharesOf', args: [userAddress], }) // 3. Parse share amount using vault decimals (NOT the output token's decimals) const parsedShares = parseUnits(humanShares, vault.decimals) if (parsedShares <= 0n) throw new Error('Redeem amount must be greater than zero') if (parsedShares > activeShareBalance) { throw new Error(`Insufficient redeemable share balance: have ${activeShareBalance}, need ${parsedShares}`) } // 4. Submit redeem — no approval required, vault locks shares from caller const hash = await walletClient.writeContract({ address: queue.queue as `0x${string}`, abi: REDEEM_QUEUE_ABI, functionName: 'redeem', args: [parsedShares], }) return publicClient.waitForTransactionReceipt({ hash }) } ``` **No approval needed:** Unlike deposits, redemptions do not require an ERC20 `approve`. The vault contract has the authority to lock and burn shares on behalf of the caller. ### Sync redeem queues A `SyncRedeemQueue` pays out in the same transaction. There is no request, no `handleBatches()` and no claim. Check `queue.is_paused === false`. Read `shareManager.activeSharesOf(userAddress)` and validate `parsedShares` as for async queues. Call `remainingDailyLimit()` and use the second value. The redemption reverts with `DailyLimitOverflow` if `parsedShares` exceeds it. The limit is shared by all users of the queue and refills linearly over 24 hours. Read `syncRedeemParams()` for `penaltyD6` and `maxAge`, and `oracle.getReport(queue.asset)` for the price. The report must not be suspicious and must be younger than `maxAge`. Then compute: ```typescript theme={null} let net = shares * (1_000_000n - penaltyD6) / 1_000_000n net -= net * redeemFeeD6 / 1_000_000n // feeManager.redeemFeeD6() const assets = net * 10n ** 18n / report.priceD18 ``` Check that `assets <= getLiquidAssets()`, or the call reverts with `InsufficientAssets`. Call `redeem(parsedShares, receiverAddress)`. No ETH value and no approval are needed. Listen for the `Redeemed` event to confirm the amount sent. `redeem` has no minimum-output argument. The oracle report, parameters and liquidity can change between your estimate and execution, so show users an estimate rather than a guaranteed amount. ## 10. Claim Redeem ### Overview A user may accumulate multiple redemption requests over time. The `requestsOf` function returns all requests paginated. Once the oracle marks a request `isClaimable`, the user can batch-claim them by passing the corresponding timestamps to `claim`. ### Steps Paginate `requestsOf(userAddress, offset, 100)`: * Start with `offset = 0`. * Increment by `100` each iteration. * Stop when a page returns fewer than `100` items. Filter to requests where `isClaimable === true`. Extract the `timestamp` field from each claimable request. Cast to `number` - timestamps are `uint32` values, safely representable as JavaScript numbers until year 2106. Call `claim(userAddress, timestamps)`. Returns the total `assets` transferred. **`claim()` is idempotent.** Non-claimable or already-claimed timestamps are silently skipped – the contract does not revert. You may safely pass all known timestamps and let the contract filter them. ### Code Example ```typescript theme={null} async function claimRedeem(vault: VaultData, queueAddress: string) { const userAddress = account.address const PAGE_SIZE = 100 // 1. Collect all requests via pagination const allRequests: RedeemRequest[] = [] let offset = 0 while (true) { const page = await publicClient.readContract({ address: queueAddress as `0x${string}`, abi: REDEEM_QUEUE_ABI, functionName: 'requestsOf', args: [userAddress, BigInt(offset), BigInt(PAGE_SIZE)], }) as RedeemRequest[] allRequests.push(...page) if (page.length < PAGE_SIZE) break offset += PAGE_SIZE } if (allRequests.length === 0) { throw new Error('No redemption requests found') } // 2. Filter to claimable requests const claimable = allRequests.filter(r => r.isClaimable) if (claimable.length === 0) { throw new Error( `${allRequests.length} redemption request(s) are pending oracle processing — try again later`, ) } // 3. Extract timestamps as number[] (uint32 — safe as JS number) const timestamps = claimable.map(r => Number(r.timestamp)) console.log(`Claiming ${claimable.length} of ${allRequests.length} redemption request(s)...`) // 4. Batch claim const hash = await walletClient.writeContract({ address: queueAddress as `0x${string}`, abi: REDEEM_QUEUE_ABI, functionName: 'claim', args: [userAddress, timestamps], }) const receipt = await publicClient.waitForTransactionReceipt({ hash }) const pending = allRequests.length - claimable.length if (pending > 0) { console.log(`${pending} request(s) are still pending and will need to be claimed later.`) } return receipt } ``` ## 11. Fees Fees in Mellow Core Vaults are paid in **vault shares**, not in underlying assets. The `FeeManager` contract calculates and deducts fees automatically during oracle report handling – integrators do not call fee functions directly. | Fee Type | When Applied | Effect on Integrator | | - | - | - | | **Deposit fee** | When the oracle report is handled (async) or at `deposit()` (sync) | User receives fewer shares than the raw price implies. Calculated as `shares * depositFeeD6 / 1e6`. | | **Redeem fee** | At `redeem()` time (async and sync) | A portion of shares is deducted before the redemption amount is finalized. | | **Performance fee** | Oracle report trigger | Accrued to the vault as yield is generated; does not directly affect per-request calculations. | | **Protocol fee** | Continuous accrual | Time-based, deducted from share supply; transparent to depositors but reduces NAV per share over time. | > Fees are vault-specific and set by Curators. Check the vault configuration or the Mellow API for exact fee parameters before displaying estimated returns to users. ## 12. Error Reference | Error | Contract | When it occurs | What to do | | - | - | - | - | | `PendingRequestExists` | DepositQueue | `deposit()` called when a pending request already exists | Call `cancelDepositRequest()` first, or wait for oracle and then `claim()` | | `ClaimableRequestExists` | DepositQueue | `cancelDepositRequest()` called when request is already claimable | The oracle processed the request – call `claim()` instead | | `NoPendingRequest` | DepositQueue | `cancelDepositRequest()` called with no pending request | Nothing to cancel | | `QueuePaused` | Both | Queue is temporarily suspended | Check `queue.is_paused` before submitting; wait for it to re-open | | `DepositNotAllowed` | DepositQueue, SyncDepositQueue | Vault has a whitelist and the address or Merkle proof is not included | Check `flags.hasWhitelist`; if true, obtain a valid Merkle proof via the Mellow API | | `ZeroValue` | RedeemQueue | `redeem()` called with `shares = 0` | Validate amount > 0 before calling | | `ERC20InsufficientBalance` | Both | Token or share balance too low | Validate balance onchain before submitting | | `StaleReport` | Sync queues | Latest oracle report is older than the queue's `maxAge` | Wait for the next oracle report, or use an async queue | | `InvalidReport` | Sync queues | Latest oracle report is suspicious or has price 0 | Wait until the report is accepted or replaced | | `DailyLimitOverflow` | SyncRedeemQueue | Redemption exceeds the remaining daily limit | Check `remainingDailyLimit()`; redeem less or wait for the limit to refill | | `InsufficientAssets` | SyncRedeemQueue | Not enough liquid assets in the vault and subvaults | Check `getLiquidAssets()`; redeem less or use an async queue | | `Forbidden` | Both | Caller does not have the required role for the called function | This is a contract-operator error; user-facing code should not hit this | ## 13. Events Reference Listen for these events to drive UI state or index onchain activity. | Event | Contract | Emitted when | | - | - | - | | `DepositRequested(account, referral, assets, timestamp)` | DepositQueue | `deposit()` succeeds | | `DepositRequestClaimed(account, shares, timestamp)` | DepositQueue | `claim()` succeeds – user received vault shares | | `DepositRequestCanceled(account, assets, timestamp)` | DepositQueue | `cancelDepositRequest()` succeeds – tokens returned | | `RedeemRequested(account, shares, timestamp)` | RedeemQueue | `redeem()` succeeds | | `RedeemRequestsHandled(counter, demand)` | RedeemQueue | `handleBatches()` settled one or more batches – **listen for this to know when claims become available** | | `RedeemRequestClaimed(account, receiver, assets, timestamp)` | RedeemQueue | `claim()` succeeds – user received underlying assets | | `Deposited(account, referral, assets, shares, feeShares)` | SyncDepositQueue | `deposit()` succeeds – shares minted to the user in the same transaction | | `Redeemed(account, shares, assets, feeShares)` | SyncRedeemQueue | `redeem()` succeeds – assets sent to the receiver in the same transaction | ### Example: watching for claim events with viem ```typescript theme={null} const unwatch = publicClient.watchContractEvent({ address: depositQueueAddress as `0x${string}`, abi: DEPOSIT_QUEUE_ABI, eventName: 'DepositRequestClaimed', args: { account: userAddress }, onLogs: (logs) => { for (const log of logs) { console.log(`Shares received: ${log.args.shares}, timestamp: ${log.args.timestamp}`) } }, }) // Stop watching when done unwatch() ``` # MellowAccountV1 Source: https://docs.mellow.finance/core-vaults/architecture/accounts/mellowaccountv1 ### Overview `MellowAccountV1` is a minimal smart account for curators. It is owned by a single address, which can execute a batch of calls in one transaction. Its main use is as the curator entity of a vault. You grant the account the roles a curator needs, such as the verifier `CALLER_ROLE`, `PUSH_LIQUIDITY_ROLE` or `PULL_LIQUIDITY_ROLE`. The owner then combines operations like `Vault.pushAssets`, `Subvault.call` and `Vault.pullAssets` into a single atomic transaction. ### Deployment `MellowAccountV1` is an upgradeable factory entity. Instances are created through an account factory as proxies, and the implementation disables initializers in its constructor. ```solidity theme={null} function initialize(bytes memory data) external initializer ``` `data` is `abi.encode(address owner)`. The function sets the owner, initializes the reentrancy guard and emits `Initialized(data)`. ### Function: `multicall` ```solidity theme={null} struct Call { address target; bytes data; } function multicall(Call[] calldata calls) external nonReentrant onlyOwner returns (bytes[] memory results) ``` * Executes each call in order, as a regular call from the account to `target` with `data`. * Returns the return data of every call. * Reverts the whole batch if any call reverts, bubbling up the original revert reason. * Emits `Executed(caller)`. Calls send no ETH, and the account has no `receive` function. ### Ownership Ownership uses OpenZeppelin `OwnableUpgradeable`. The owner can transfer or renounce ownership with the standard functions. Renouncing ownership leaves the account with no one who can call `multicall`. ### Events ```solidity theme={null} event Executed(address indexed caller); ``` ### Security considerations * The account can do anything its roles allow, so protect the owner key accordingly. A multisig is recommended for production vaults. * The account adds no checks of its own. All permission checks happen in the vault, subvault and verifier it calls. ### Audit Audited by Nethermind (NM\_0829, February 2026). See [Security](/security). # Factory Source: https://docs.mellow.finance/core-vaults/architecture/factory #### Overview The `Factory` contract is a generalized deployment mechanism for creating upgradeable proxy instances of pre-approved implementations. It tracks multiple implementation versions, proposal workflows, blacklisting for security, and ownership-based access control. It conforms to the `IFactory` interface and supports deploying any `IFactoryEntity`-compliant contracts via `TransparentUpgradeableProxy`, using `initialize()` for configuration. #### Key Capabilities * **Versioned Deployment**: Track multiple logic contract versions, each deployable by index. * **Proposal System**: Allow anyone to propose implementations, with owner approval required. * **Blacklist Mechanism**: Prevent deployment of insecure or deprecated versions. * **Deterministic Deployments**: Uses `create2` salt to ensure predictable addresses. * **Entity Tracking**: Keeps a registry of all deployed entities. #### Storage Structure The contract uses a deterministic storage layout via: ```solidity theme={null} bytes32 _factoryStorageSlot = SlotLibrary.getSlot("Factory", name_, version_); ``` Storage fields (in `FactoryStorage`) include: | Field | Description | | - | - | | `entities` | Set of all deployed proxy instances | | `implementations` | Approved logic contract addresses | | `proposals` | Pending implementation proposals | | `isBlacklisted` | Mapping from version → is blacklisted | #### Initialization ```solidity theme={null} function initialize(bytes calldata data) external initializer ``` * Accepts encoded owner address * Sets initial admin and emits `Initialized` #### Entity Deployment ```solidity theme={null} function create(uint256 version, address owner, bytes calldata initParams) external returns (address instance) ``` Deploys a new `TransparentUpgradeableProxy`: * Uses implementation from `implementations.at(version)` * Rejects if version is out-of-bounds or blacklisted * Uses `salt = keccak256(version, owner, initParams, currentEntityCount)` for deterministic deployment * Calls `initialize(initParams)` on the new proxy Emits: ```solidity theme={null} event Created(address instance, uint256 version, address owner, bytes initParams); ``` #### Implementation Management #### Propose New Implementation ```solidity theme={null} function proposeImplementation(address implementation) external ``` * Fails if already in `implementations` or `proposals` * Adds to `proposals` * Emits: `ProposeImplementation(implementation)` * Permissionless function #### Accept Proposed Implementation ```solidity theme={null} function acceptProposedImplementation(address implementation) external onlyOwner ``` * Only callable by owner * Fails if not proposed * Moves from `proposals` → `implementations` * Emits: `AcceptProposedImplementation(implementation)` #### Blacklisting ```solidity theme={null} function setBlacklistStatus(uint256 version, bool flag) external onlyOwner ``` * Blocks deployments using specific version * Enforces that version index exists * Emits: `SetBlacklistStatus(version, flag)` #### View Functions | Function | Returns | | - | - | | `entities()` | Total number of deployed entities | | `entityAt(index)` | Deployed entity at index | | `isEntity(address)` | Checks if address is a deployed entity | | `implementations()` | Total implementation count | | `implementationAt(index)` | Implementation at index | | `proposals()` | Total proposals pending approval | | `proposalAt(index)` | Proposal at index | | `isBlacklisted(version)` | Whether a version is blocked from deployment | #### Access Control * Uses `OwnableUpgradeable` * Only owner can accept implementations and blacklist versions #### Security Considerations * **Immutable logic whitelist**: Only approved contracts can be deployed * **Blacklisting**: Emergency response for vulnerabilities * **Replay protection**: Deployment salt ensures unique addresses * **Decentralized proposals**: Anyone can propose implementations, but only owner can accept # BasicRedeemHook Source: https://docs.mellow.finance/core-vaults/architecture/hooks/basicredeemhook ### Overview `BasicRedeemHook` is a minimal hook implementation for `IHook`, designed to dynamically fetch liquidity from subvaults during redemption processing in a `VaultModule`. It ensures that enough assets are available for user redemptions by pulling liquidity from a set of registered subvaults. This hook is typically invoked by a vault's redemption queue or during `redeem()` operations when assets must be made liquid. ### Purpose * Ensures **sufficient liquidity** in the vault to fulfill asset redemptions. * Minimizes idle capital by **pulling only when needed**. * Supports **liquidity routing** across subvaults. ### Key Functions ### `callHook(address asset, uint256 assets)` Attempts to make `assets` of `asset` liquid within the main vault by pulling from subvaults if needed. **Execution Flow:** 1. Checks how much of the `asset` the vault already holds. 2. If balance is sufficient → no-op. 3. Otherwise: * Iterates over subvaults (via `subvaultAt(i)`) * Skips subvaults where the `RiskManager` does not allow the asset (`isAllowedAsset(subvault, asset)`) * For each remaining subvault: * Pulls **only the required portion** via `hookPullAssets()` * Stops when total required assets have been pulled **Guarantees:** * Only pulls the **exact missing amount**, no over-pulling * Efficient: stops once liquidity need is satisfied * Skips subvaults with `0` balance ### `getLiquidAssets(address asset) → uint256` Returns the **total liquid amount of a given asset** available across the vault and its subvaults. Unlike `callHook`, this function is called directly by the vault (`ShareModule.getLiquidAssets`), not through `delegatecall`, so it treats `msg.sender` as the vault. * Reads balances: * `vault.balanceOf(asset)` * `subvault[i].balanceOf(asset)` for each subvault where the `RiskManager` allows the asset * Aggregates and returns sum Only token balances held directly by the vault and subvaults count. Positions in external protocols are not included. ### Contract Assumptions * The `vault` invoking this hook implements `IVaultModule` and supports: * `subvaults()` → total number of subvaults * `subvaultAt(index)` → address of a given subvault * `hookPullAssets(subvault, asset, amount)` → callable method to move funds * `riskManager()` → used to check which subvaults allow the asset ### Security Considerations * Hook only pulls assets using vault-controlled `hookPullAssets()`, ensuring controlled asset flow. * Assumes vault validates which hook is active – no permissioning within the hook itself. # Hooks Source: https://docs.mellow.finance/core-vaults/architecture/hooks/index Hooks let a vault run custom asset logic whenever a queue moves assets in or out. Typical uses are routing deposits into subvaults, wrapping assets, and pulling liquidity back for redemptions. ### How hooks are called * Every hook implements `IHook.callHook(address asset, uint256 assets)`. * Queues trigger hooks through `ShareModule.callHook(assets)`. The vault resolves the hook for the calling queue and runs it with `delegatecall`, so the hook code executes in the vault's context. * Each queue uses its custom hook if one is set with `setCustomHook(queue, hook)`. Otherwise it falls back to `defaultDepositHook` or `defaultRedeemHook`. Changing hooks requires `SET_HOOK_ROLE`. * For redeem queues, the vault transfers `assets` to the queue after the hook returns, or straight away if the queue has no hook. | Queue | When the hook runs | | - | - | | `DepositQueue` | When an oracle report processes pending deposits | | `SignatureDepositQueue` | Inside the signed deposit transaction | | `SyncDepositQueue` | Inside the deposit transaction | | `RedeemQueue` | When `handleBatches` settles priced batches | | `SignatureRedeemQueue` | Inside the signed redemption transaction | | `SyncRedeemQueue` | Inside the redemption transaction | ### Redeem hooks Redeem hooks also implement `IRedeemHook.getLiquidAssets(address asset)`. Redeem queues call `ShareModule.getLiquidAssets()` to find out how much they can pay out, and the vault answers with the hook's `getLiquidAssets`. Without a redeem hook, only the vault's own balance counts as liquid. ### Hook contracts In this directory, you will find a detailed per-contract overview of the "Hooks" contract category, including the following hooks: [BasicRedeemHook](/core-vaults/architecture/hooks/basicredeemhook) [LidoDepositHook](/core-vaults/architecture/hooks/lidodeposithook) [RedirectingDepositHook](/core-vaults/architecture/hooks/redirectingdeposithook) [TransferDepositHook](/core-vaults/architecture/hooks/transferdeposithook) # LidoDepositHook Source: https://docs.mellow.finance/core-vaults/architecture/hooks/lidodeposithook ### Overview `LidoDepositHook` is an implementation of the `IHook` interface that acts as a **conversion adapter** for incoming deposits. It standardizes various ETH-like assets into **`wstETH`** for use in downstream vault logic. The hook supports **ETH**, **WETH**, and **stETH** as input formats and ensures conversion to `wstETH` before optionally forwarding execution to a downstream `nextHook`. ### Primary Purpose * Converts **ETH**, **WETH**, or **stETH** into **`wstETH`** on deposit. * Ensures compatibility with protocols that expect `wstETH`. * Provides **composable hooks** by chaining into a downstream `IHook` (`nextHook`). ### Constructor Parameters | Parameter | Type | Description | | - | - | - | | `wsteth_` | address | Address of the `wstETH` token contract | | `weth_` | address | Address of the `WETH` token contract | | `nextHook_` | address | Address of the optional downstream hook to forward to after wrapping | ### Key Function ### `callHook(address asset, uint256 assets)` Handles conversion of an input asset into `wstETH` and optionally delegates the call to a downstream hook. **Supported Input Types:** 1. `wstETH` - forwarded directly 2. `stETH` - wrapped into `wstETH` via `IWSTETH(wsteth).wrap()` 3. `WETH` - unwrapped into ETH via `IWETH(weth).withdraw()`, then deposited into `wstETH` 4. `ETH` (i.e. `address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE)`) – directly deposited into `wstETH` **Execution Steps:** * If `asset == wstETH`: do nothing * If `asset == stETH`: * Approves `wstETH` to pull `stETH` * Calls `wrap()` on `wstETH` to convert to `wstETH` * If `asset == WETH`: * Unwraps to ETH using `withdraw()` * Sends ETH to `wstETH` contract to mint `wstETH` * If `asset == ETH`: * Sends ETH directly to `wstETH` * After conversion: * Calculates how much new `wstETH` was received * If `nextHook` is configured, delegates `callHook(wstETH, amount)` to it ### Errors * `UnsupportedAsset(address asset)` - Thrown if the provided asset is neither `wstETH`, `stETH`, `WETH`, nor `ETH` ### Assumptions: It is assumed this hook will not trigger `STAKE_LIMIT` or other limit-related errors in the Lido contracts. If such errors do occur, the vault admin can reconfigure the system to bypass the automated staking hook. In this case, `RedirectingDepositHook` can be assigned to the relevant queues, delegating staking responsibilities to the vault curator via manual liquidity management. # RedirectingDepositHook Source: https://docs.mellow.finance/core-vaults/architecture/hooks/redirectingdeposithook ### Overview `RedirectingDepositHook` is a deposit-time liquidity allocation hook implementing the `IHook` interface. It is designed to **redirect newly deposited assets** from a vault into its underlying **subvaults**, based on per-subvault risk and capacity constraints defined by a `RiskManager`. This hook helps distribute liquidity optimally during deposit flows. ### Purpose * Automatically **forwards newly deposited assets** from the main vault into eligible subvaults. * Delegates decision-making to the vault's configured `RiskManager`, which determines per-subvault deposit limits. * Ensures that **subvaults do not exceed their capacity constraints**. ### Key Function ### `callHook(address asset, uint256 assets)` Distributes a given amount of `asset` across available subvaults based on their individual deposit capacity. **Execution Logic:** 1. Retrieves: * Active `vault` context via `IVaultModule(address(this))` * Configured `RiskManager` via `vault.riskManager()` * Number of subvaults via `vault.subvaults()` 2. Iterates over each subvault: * Fetches max allowed deposit via `riskManager.maxDeposit(subvault, asset)` * If allowed amount is zero → skip * Otherwise: * Pushes `min(assets, allowed)` via `vault.hookPushAssets()` * Decrements `assets` accordingly * Stops early if full amount has been distributed ### Components and Assumptions * **Vault:** Must implement `IVaultModule`: * `subvaults()` → number of subvaults * `subvaultAt(index)` → returns subvault address * `hookPushAssets(subvault, asset, amount)` → transfers tokens to subvault * `riskManager()` → returns associated `IRiskManager` * **Risk Manager:** Must implement: * `maxDeposit(subvault, asset)` → returns max allowed deposit into that subvault # TransferDepositHook Source: https://docs.mellow.finance/core-vaults/architecture/hooks/transferdeposithook ### Overview `TransferDepositHook` is a deposit hook implementing the `IHook` interface. It forwards every processed deposit of one asset into a fixed subvault, then makes that subvault transfer the same amount to a fixed destination address. Use it when deposits must leave the vault right away for a known external destination, such as a custody account or an external strategy wallet, instead of staying idle in the vault. A single hook instance is bound to one vault, one asset, one subvault and one destination. All four are set in the constructor and cannot be changed. ### Constructor parameters | Parameter | Type | Description | | - | - | - | | `asset_` | address | The only asset the hook accepts | | `vault_` | address | The vault that delegate-calls the hook | | `subvault_` | address | Subvault that receives the deposit and performs the transfer | | `to_` | address | Final destination of the deposited assets | The values are exposed as public immutables: `asset()`, `vault()`, `subvault()` and `to()`. ### Key function ### `callHook(address asset_, uint256 assets_)` The vault calls the hook through `delegatecall` from `ShareModule.callHook`, so the code runs in the vault's context. **Execution logic:** 1. Reverts with `OnlyDelegateCall` if called directly instead of through `delegatecall`. 2. Reverts with `InvalidVault` if the calling context is not the configured `vault`. 3. Reverts with `InvalidAsset` if `asset_` is not the configured `asset`. 4. Reverts with `UnsupportedSubvault` if the configured subvault is no longer connected to the vault (`vault.hasSubvault(subvault)`). 5. Returns early if `assets_` is zero. 6. Calls `vault.hookPushAssets(subvault, asset, assets_)`. This moves the assets into the subvault and increases the subvault balance in the `RiskManager`. 7. Calls `ICallModule(subvault).call(asset, 0, transfer(to, assets_), payload)` with an empty `ONCHAIN_COMPACT` verification payload. The subvault transfers `assets_` to `to`. 8. Reverts with `InvalidResponse` if the token returns data other than `true`. Tokens that return no data, such as USDT, are accepted. ### When the hook runs * **`DepositQueue`**: when an oracle report processes pending deposits, for the assets moved into the vault in that report. * **`SyncDepositQueue`**: inside the user's `deposit` transaction. ### Configuration requirements The hook only works if the vault is configured for it: * Assign it to the deposit queue for `asset` with `setCustomHook(queue, hook)`, or as the default deposit hook with `setDefaultDepositHook(hook)`. Both require `SET_HOOK_ROLE`. If you set it as the default, every deposit queue in the vault must use `asset`, because any other asset reverts with `InvalidAsset`. * The subvault's `RiskManager` configuration must allow `asset` for that subvault and leave enough limit for incoming deposits. Otherwise `hookPushAssets` reverts. * The vault address must hold the verifier `CALLER_ROLE`, because the vault itself calls `subvault.call(...)`. * The subvault's verifier must allow the compact call `(who = vault, where = asset, selector = transfer)` through `allowCalls`. The hook always sends an `ONCHAIN_COMPACT` payload, so a Merkle-only permission is not enough. ### Accounting note `hookPushAssets` records the deposit as subvault balance in the `RiskManager`. The transfer from the subvault to `to` is a plain token transfer, so it does not reduce that recorded balance. The oracle report for the vault must account for the assets held at `to` so that the share price stays correct. ### Errors | Error | Reason | | - | - | | `OnlyDelegateCall()` | Hook called directly instead of via `delegatecall` | | `InvalidVault()` | Calling context is not the configured vault | | `InvalidAsset()` | Asset passed to the hook is not the configured asset | | `UnsupportedSubvault()` | Configured subvault is not connected to the vault | | `InvalidResponse()` | Token `transfer` returned unexpected data | ### Audit `TransferDepositHook` was reviewed in the Nethermind custom hook audit (NM\_0956, June 2026). See [Security](/security). # Architecture Source: https://docs.mellow.finance/core-vaults/architecture/index Full technical architecture: Deposit Queue, Redeem Queue, Signature Queues, Sync Queues, Hooks, Vault modules, Subvaults, Verifiers, Oracle, Share Manager, Fee Manager, Risk Manager, Access Control, supported protocols ## Mellow Core Vaults ### Abstract The DeFi market continues to expand with an increasing number of yield opportunities and protocols, each with its own level of decentralization, permissionlessness, and risk. Navigating this landscape is becoming challenging – even for advanced users. Meanwhile, the latest wave of adoption is bringing thousands of new participants from traditional markets into crypto. These users aren't looking for raw protocols – they expect products that are structured, accessible, and configurable to meet their compliance requirements. To meet this demand, the onchain financial stack must evolve. It needs to be productized, abstracted and made composable. Mellow's response is **Core Vaults** – infrastructure that lets curators and asset managers design, deploy, and scale structured products onchain. ### Vaults Architecture Core Vaults are built around a modular architecture that orchestrates interactions across both DeFi protocols and centralized exchanges. It enables trustless execution of complex, institutional-grade strategies while maintaining full composability and transparency. No matter how diverse or sophisticated the underlying strategies become, the vault framework remains stable and predictable – providing a unified, programmable setup for managing assets, risks, and logic. The result is infrastructure that supports scalable, curated access to onchain yield – secure, standardized, and ready for real users. #### Core Vaults Features & Functionality * Deposits & Withdrawals for the Liquidity Providers * Valuation Oracle * Strategy performance analytics for Liquidity Providers * Fee management * Vault management tools & Curation UI * Reward distribution engine * Receipt token distribution infrastructure * Smart contract safeguards * Granular role-based control * Access to external apps and protocols #### Vault system architecture overview Vault system architecture overview #### Deposit Queue Manages user deposits through a time-buffered queuing system. This delay helps prevent front-running and price manipulation by ensuring deposits are not executed based on stale or externally influenced oracle price data. The deposit queue is also responsible for issuing receipt tokens. * **Technical Details\`** **Deposit flow:** * Step 1: **User Deposits** * A user submits a deposit request via `deposit(assets, referral, merkleProof)`. * Deposits are validated via optional Merkle whitelist logic (using `merkleProof`) or onchain mapping (if `hasWhitelist` flag is set). * If a previous request exists and is already claimable, it is claimed automatically. If it is still pending, the new deposit reverts until the request is processed or canceled. * The deposited amount and timestamp is stored in the `DepositQueue` contract * Step 2: **Oracle Report** * Oracle report is propagated via the `handleReport(priceD18, timestamp)` method. * The queue validates the report: * It must be called by the `Vault`. * Provided `timestamp` must be in the past (usually `request.timestamp - depositInterval`). * `priceD18` must be non-zero. * The queue handles deposit requests that are pending for at least `depositInterval` seconds (the interval is specified in the oracle's security parameters). * The contract stores a `(timestamp, reducedByDepositFeePriceD18)` pair in the `prices` array. This value is used to convert accumulated assets into shares based on the user's request timestamp. The `reducedByDepositFeePriceD18` is derived by applying the deposit fee to the actual reported `priceD18`. * The corresponding shares are allocated but not yet minted. * Emits the `ReportHandled` event. * Step 3: **User Claims** * A user calls `claim(account)` (or `claimShares(account)` in the `Vault`) to mint and receive previously allocated shares. * The number of shares is computed as: ```solidity theme={null} uint256 shares = (request.assets * reducedByDepositFeePriceD18) / 1e18; ``` * Shares are minted to the user via `mintAllocatedShares`. #### Assumptions & Properties 1. Single Active Request Each user may have at most one unprocessed deposit request. New deposits are blocked until the previous request is claimed. If the pending request is already claimable, the claim will be automatically processed during the next deposit. 2. Delayed Execution Deposit processing requires an Oracle report submitted after a configured `depositInterval`. 3. Lazy Claiming Deposits are converted into shares during oracle processing, but users must call the claim function (in `ShareModule`, `ShareManager` or in each `DepositQueue` separately) to receive them. However, even without explicitly claiming, the user's full share balance – including all claimable shares across all deposit queues – is accurately reflected in `shareManager.sharesOf(user)`. 4. Whitelist Enforcement Deposits may require Merkle proof for depositor whitelisting.\ \\
#### Redeem Queue Serving as the counterpart to the Deposit Queue, it manages withdrawal requests. Redemptions are processed in two phases: 1. Oracle pricing – reports are processed in batches, with each batch assigned a specific conversion price derived from its corresponding Oracle report. 2. Liquidity settlement – Vault liquidity is pulled asynchronously, allowing the curator to finalize withdrawals and perform asset swaps before processing user redemption requests. This separation allows asynchronous liquidity management, gas efficiency, and protection against griefing. Unlike deposit requests, withdrawal requests in the Redeem Queue cannot be canceled to prevent yield-griefing. * **Technical Details** **Redeem flow**: * **Step 1: User redeems** * User submits `redeem` request, vault shares are immediately locked in the share manager. They are burned when an oracle report prices the request. * The vault curator monitors and manages liquidity across connected Subvaults, pulling funds and swapping assets as needed to fulfill redemption requests. * **Step 2: Oracle report** * Valid and non-suspicious `Oracle` `report` arrives. * Anyone, usually the vault curator or a keeper, invokes `handleBatches(n)` on the `RedeemQueue`. * This triggers the movement of required assets from the vault (and associated subvaults) to process redemption requests. * **Step 3: User claims** * Users call `claim(receiver, timestamps)` to withdraw assets. #### Assumptions & Properties 1. Non-Cancellable Requests Prevents griefing where a user requests redemption, causing curator to pull liquidity, then cancels. 2. Time-sensitive request handling Oracle reports can only process a redemption request if at least `redeemInterval` seconds have passed since the request was submitted – i.e., `report.timestamp` must be greater than or equal to `request.timestamp + redeemInterval`. 3. Asynchronous Fulfillment Liquidity can be managed independently of oracle report submission.\ \\
#### Signature Deposit and Redeem Queues Signature Queues allow deposits and redemptions to be processed instantly, bypassing the standard time-buffered flow. That happens when a trusted consensus group issues offchain-signed approvals. Key Features: * **Instant execution** without waiting for Oracle price reports * **Nonce-based signature protection** to prevent replay attacks * **EIP-712/EIP-1271 compatible** signed orders * **Oracle price validation** enforced onchain * **Stateless and removable** (does not accumulate shares or process claims) * **Fee-bypass**: No Deposit Fee or Redeem Fee is charged for actions via this queue * **Technical Details** **Signature Queue flow:** * **Step 1: Offchain signing** * Offchain consensus actors (operators, curators, admins) generate signed `Order` messages. * **Step 2: Onchain execution** * A user submits this order to the `SignatureQueue` contract for execution. * The queue: * Verifies the order signature * Validates nonce, queue address, asset match, deadline, and caller * Computes the implied asset/share price and checks it against the vault's oracle * If all checks pass, the order is executed atomically. #### Assumptions & Properties * Only **trusted offchain actors** (consensus group) are authorized to sign orders. * Price quotes must be valid and non-suspicious. * Users cannot reuse old signatures due to nonce tracking. * Orders must be executed before `deadline`.\ \\
#### Sync Deposit and Redeem Queues Sync Queues settle deposits and redemptions instantly at the latest oracle price, without signatures and without a claim step. Any eligible user can call them directly. Key Features: * **Instant execution** at the latest stored oracle report * **Freshness check**: the report must be non-suspicious and no older than `maxAge` * **Optional penalty** (`penaltyD6`) that reduces the shares minted or the assets paid out, in favor of remaining holders * **Fees apply**: the vault's deposit and redeem fees are charged as in the async queues * **Redemption safeguards**: `SyncRedeemQueue` also checks vault liquidity and a leaky-bucket daily limit in shares * **Technical Details** **Sync deposit flow:** * User calls `deposit(assets, referral, merkleProof)` on the `SyncDepositQueue`. * The queue checks the whitelist and the oracle report, moves the assets into the vault, mints shares to the user and the fee recipient, and runs the deposit hook. **Sync redeem flow:** * User calls `redeem(shares, receiver)` on the `SyncRedeemQueue`. * The queue checks the oracle report, liquidity and daily limit, burns the shares, runs the redeem hook to pull liquidity, and sends the assets to `receiver`. #### Assumptions & Properties * Prices can be up to `maxAge` old, so `maxAge` should stay close to the oracle's reporting cadence. * Sync redemptions do not reserve liquidity for priced async redemptions. When both queue types are active, the curator must keep enough liquid assets for both. * See [SyncDepositQueue](/core-vaults/architecture/queues/syncdepositqueue) and [SyncRedeemQueue](/core-vaults/architecture/queues/syncredeemqueue) for details.\ \\
#### Hooks Hooks are contracts that a vault runs with `delegatecall` whenever a queue moves assets in or out. Deposit hooks route or convert incoming assets, for example forwarding them into subvaults or wrapping ETH into wstETH. Redeem hooks pull liquidity back from subvaults and report how much is available for redemptions. Each queue uses its own custom hook if one is set, or the vault's default deposit or redeem hook. See [Hooks](/core-vaults/architecture/hooks/index) for the available implementations. #### Vault The Vault contract serves as the central entry point into the Core Vault system. It is configured by four internal modules: * **BaseModule**: Implements different auxiliary interfaces such as `onERC721Received`, `getStorageAt` and `receive` callback * **ACLModule:** Role-based access control for system components * **ShareModule:** Management of shares, fees, deposit and redeem queues lifecycle, `Oracle` report handling * **VaultModule:** Subvaults management, pushing and pulling of assets in subvaults\ \\
#### Subvault The Subvault contract is a vault component designed to manage delegated asset strategies, acting as a controlled execution unit within a system. Like the Vault contract, it is configured by modules: * **BaseModule**: Implements different auxiliary interfaces such as `onERC721Received`, `getStorageAt` and `receive` callback. * **SubvaultModule:** Represents an isolated child vault within a modular vault system, responsible for securely holding and releasing assets upon authenticated requests. * **VerifierModule:** is an abstract extension of the Base Module designed to provide standardized access to a Verifier contract. * **CallModule:** Enables arbitrary low-level calls to external contracts (used by curator of the vault), and verification through a verifier module.\ \\
#### Verifier Each Subvault is paired with a Verifier contract, which validates function calls and ensures only pre-approved actions from valid actors are permitted across vault-connected modules. While each Subvault is expected to have its own dedicated Verifier contract, it is still possible for the same Verifier to be shared across multiple Subvaults. * **Technical Details** Verifier allows multiple types of verification: * **ONCHAIN\_COMPACT**: Checks `CompactCall` (who | where | selector) hash against internal admin-controlled set * **MERKLE\_COMPACT**: Verifies Merkle proof of `CompactCall` (who | where | selector) hash * **MERKLE\_EXTENDED**: Verifies Merkle proof of `ExtendedCall` (who | where | value | callData) hash * **CUSTOM\_VERIFIER:** Delegates full verification to an external verifier Two admin-owned parameters are saved in the state of the Verifier contract: `compactCallHashes` that defines all allowed calls for **ONCHAIN\_COMPACT** verification type `compactCalls` is an optional mapping for reverse lookup of call metadata by hash `merkleRoot` that defines all allowed calls for **MERKLE\_COMPACT, MERKLE\_EXTENDED** and **CUSTOM\_VERIFIER**\ \\
#### Oracle The Oracle contract handles secure and configurable price reporting for supported assets. Closely integrated with the `ShareModule`, it provides price validation, deviation monitoring, and time-restricted report submissions. It enforces strict guarantees around **report timing** and **trust minimization** using role permissions and deviation thresholds. This oracle ensures consistent pricing across all queue, share, and limit-related calculations. Key considerations: * Only authorized roles can submit price updates * Suspicious reports require explicit approval before acceptance * Price validation is performed locally without relying on external oracle feeds * Manipulation is prevented through absolute and relative deviation limits * **Technical Details** Oracle Configurable Security Parameters: * **Absolute Deviation**: Hard limits on price delta in price units * **Relative Deviation**: Tolerance as a percentage (e.g., 5% = 0.05e18) * **Timeout**: Minimum time between valid reports (ignored if the previous report is suspicious) * **depositInterval**: Minimum age required for a deposit to be processed * **redeemInterval**: Same, but for redemptions **Reporting flow**: * **Step 1: Report is submitted:** * Each asset is checked for support * The previous report state is evaluated: * If `timeout` has not passed, and the report is not suspicious → revert `TooEarly` * Price is compared against previous: * `maxAbsolute` → revert `InvalidPrice` * `maxRelative` → flagged `isSuspicious` * If the report is valid and non-suspicious (deviation \< suspicious && deviation \< max), it is immediately accepted * If the report is suspicious it will be accepted only after validation from the Admin (ACCEPT\_REPORT\_ROLE holder) * **Step 2: Accepted report propagation:** * Triggers `vault.handleReport(...)`, processing deposit requests and pending redeem requests * Emits `ReportsSubmitted` **Validation Logic:** Reports are validated by: * Calculating **absolute deviation (**`maxAbsolute` ) * Calculating **relative deviation (**`maxRelative` ) * Comparing against `max` and `suspicious` thresholds A report is: * **Rejected** if either deviation exceeds max * **Accepted but marked as suspicious** if above the suspicious threshold, flagging the report * **Accepted as normal** if within all limits Used by: * `SignatureDepositQueue`, `SignatureRedeemQueue`, `DepositQueue` and `RedeemQueue` contracts * `SyncDepositQueue` and `SyncRedeemQueue` contracts * Vault's limit accounting (`RiskManager`)\ \\
#### Share Manager The Share Manager is an upgradeable contract responsible for managing vault share issuance, allocation, whitelisting, permissions, and lockups within a modular vault system. Key responsibilities include: * Tracking total and active share supply * Managing global and targeted account lockups * Verifying whitelist status and transfer permissions * Enforcing mint, burn, and transfer pauses * Handling share allocation and claims through queues * Whitelisting for implementing KYC & compliance features **Technical Details** Share Manager relies on a compact bitmask (`flags`) for enabling/disabling features and supports configurable per-account permissions. Controlled via `ShareManagerFlagLibrary`: * `hasMintPause` * `hasBurnPause` * `hasTransferPause` * `hasWhitelist` * `hasTransferWhitelist` * `globalLockup` * `targetedLockup` Lockups are enforced in `updateChecks`. Share Manager operates under the control of defined roles: * `SET_FLAGS_ROLE`: Allows changing global flags (e.g., mint pause, whitelist enforcement). * `SET_ACCOUNT_INFO_ROLE`: Grants permission to set per-account configuration. * `SET_WHITELIST_MERKLE_ROOT_ROLE`: Grants permission to set new whitelist merkle root.\\
#### Fee Manager The Fee Manager oversees the calculation and management of multiple fee types within the vault system. Currently, it supports four fee-acquiring methods: * **Deposit Fee:** Charged on asset deposits * **Redeem Fee:** Applied on share redemptions * **Performance Fee:** Based on decreases in share price (assets × price = shares) * **Protocol Fee:** Time-based fee accrued per vault All fees are paid in vault shares rather than the underlying assets. Admin can update recipient and fee parameters. **Technical Details** Fee Manager will require identifying the base asset of the vault. `setBaseAsset`: Called once per vault to register its performance reference token. `updateState(asset, price)`: Vaults call this to refresh timestamp and minimum price. Performance and Protocol fees are activated by a fresh Oracle report on the base asset. #### Fee Calculation Logic: * **Deposit fee** – applied linearly and calculated as `shares * depositFeeD6 / 1e6`, deducted during the oracle report handling process. * **Redeem fee** – applied linearly and calculated as `shares * redeemFeeD6 / 1e6`, deducted when shares are requested for redemption. * **Performance fee** – due to the use of a non-standard pricing mechanism (`price = shares / assets`), delegation yield will result in a lower reported price by the oracle; if `priceD18` falls below `minPriceD18`, the fee is charged as `(minPriceD18 - priceD18) * performanceFeeD6 * totalShares / 1e24` to capture the implied yield. * **Protocol fee** – a continuously accruing time-based fee calculated as `totalShares * protocolFee * (block.timestamp - timestamps[vault]) / (365 * 24 * 3600 * 1e6)`, proportional to both share supply and elapsed time since the last timestamp update.\\
#### Risk Manager The Risk Manager contract defines and enforces asset deposit limits across the Vault and its associated Subvaults. It maintains internal accounting of balances, limits, and approved assets for each subvault. This module is responsible for: * Setting and enforcing share limits (practically representing approximate deposit limits) at both vault and subvault levels * Permitting or restricting specific assets to be pulled into the Subvaults * Tracking pending balances for Deposits that are not yet finalized * Validating limits using Oracle price reports **Technical Details** * **Vault Limit**: Global cap across all assets managed by the vault (in shares). * **Subvault Limit**: Individual cap per subvault, enforced independently (in shares). * **Allowed Assets**: Only explicitly allowlisted assets are permitted for push / pull operations in a given subvault. * **Pending Assets**: Temporarily tracked assets, e.g., during deposit queueing * **Shares Conversion**: All balances are internally tracked in shares, calculated using latest report in the `Oracle` contract. All vault and subvault-level limits are treated as **approximate** and computed using the most recent Oracle report available **at the time of the state update** (on Subvault pull/push event or Deposit/Redeem operations). If actual balances deviate significantly from the stored `balance` values due to oracle drift, delayed execution, or protocol-side changes, a **trusted actor** can apply a 'corrections' to mitigate the difference: * `modifyVaultBalance` for the Vault, or * `modifySubvaultBalance` for individual Subvaults. Since the system is expected to hold only correlated assets, such manual adjustments are assumed to be **rare** under normal operating conditions.\\
#### Access Control Granular Access Control (MellowACL) is a lightweight yet extensible layer built on an OpenZeppelin contract. It adds automatic tracking and enumeration of *active roles* to enhance governance transparency and enable dynamic role management. Responsibilities include: * Granting and revoking access control roles to addresses * Maintaining a dedicated set of all active (assigned) roles * Providing enumerable functions for external auditing of granted roles * Emitting events when roles are assigned or fully revoked\\
### Supported protocols and integrations The Core Vaults architecture supports integration with a wide range of apps, including both DeFi protocols and centralized exchanges. Most of the protocol integrations can work out of the box. For the current, canonical list of supported execution venues, see [Execution venues](/mellow-vaults-overview#execution-venues). #### **CEXes** via custodial off-exchange solutions (Copper ClearLoop and Ceffu) * Deribit * Bybit * Binance * Most of tier 1 and 2 CEXes ## Case Study **Prerequisites:** The flow occurs within a Core Vault under the following conditions: 1. Two subvaults representing different yield sources: a delta-neutral trading strategy and restaking 2. Liquidity is evenly allocated 50%-50% between the subvaults 3. 1% management fee and 15% performance fee 4. Annual Percentage Rate (APR) of 10% **Initial Action:** A Liquidity Provider (LP) deposits 1,000,000 USDC into the Mellow Core Vault. **Process Flow:** 1. Deposits are processed and transferred into the vault after passing through the time-buffered Deposit Queue. 2. The LP receives receipt tokens representing the 1,000,000 USDC position. 3. The LP can use these receipt tokens as collateral in various DeFi protocols to generate additional yield, such as leverage looping on Gearbox. 4. Liquidity is allocated from the Vault to the Subvaults according to the limit-based rules. 5. 50% of unallocated funds are pulled into a Subvault 1 by a Curator. 6. 50% of unallocated funds are pulled into a Subvault 2 by a Curator. 7. The Curator allocates funds from Subvault 1 to the delta-neutral strategy through integrated centralized exchanges. 8. Funds from Subvault 2 are allocated to the restaking strategy via Symbiotic. 9. After 12 months, the LP requests a full withdrawal. 10. Throughout the holding period, Protocol and Performance Fees are automatically accrued with each Oracle update, resulting in a management fee of 10,000 USDC and a performance fee of 15,000 USDC – both paid in vault receipt tokens. 11. The withdrawal request is queued and detected onchain. 12. At the end of the withdrawal interval, the Curator transfers 1,075,000 USDC (principal plus accrued yield) from the Subvaults back to the Core Vault Contract. 13. The LP redeems the full amount directly from the Redeem Queue. *Process Flow for Curator Allocation:* 1. Curator asks Admin to add 2 subvaults with correct verifier configs: * First one allowing liquidity transfer into a Copper ClearLoop or Ceffu account * Second one allowing deposits, withdrawals, withdrawal claims and reward claims & swaps from Symbiotic 1. Curator pushes unallocated funds: `Vault.pushAssets(USDC, *500000*)`. 2. Allocates assets in Subvault 1 (Delta-Neutral strategy on Bybit via Copper ClearLoop). * In UI, Curator clicks **New Call** → sets target to **Copper Subvault** → selects **USDC** asset and **Bybit** as destination. * Generate `VerificationPayload` via Mellow API. * Executes call: ```solidity theme={null} subvault1.call( asset, // address of the asset (USDC) to be sent to the Copper account 0, // eth value abi.encodeCall( IERC20.transfer, // transfer call encoding (copperAccountAddress, 5e11) // (recipient, amount) ), verificationPayload // extra data for Verifier contract ) ``` * Inside Bybit UI, strategy is realized by the Curator, with settlements occurring every few hours in the Copper Clearloop. 3. Allocate to Subvault 2 (Symbiotic Restaking). * Pushes unallocated funds to the second subvault: `vault.pushAssets(subvault2, asset, 5e11)`. * Clicks **New Call** → target = **Restaking Subvault** → calls `deposit(subvault, 5e11)` * Gets the verification result and `VerificationPayload` from the Mellow API for this call. * Executes call: ```solidity theme={null} subvault2.call( symbioticVault, // address of the symbiotic vault 0, // eth value abi.encodeCall( ISymbioticVault.deposit, // deposit call encoding (subvault, 5e11) // (onBehalfOf, amount) ), verificationPayload // extra data for Verifier contract ) ``` 4. Curator regularly claims rewards from Symbiotic Restaking and swaps them into assets before depositing them using `subvault2.call` 5. Curator monitors Performance & Exit upon user request by repeating **New Call** steps to withdraw according to net returns. # FenwickTreeLibrary Source: https://docs.mellow.finance/core-vaults/architecture/libraries/fenwicktreelibrary This library implements a **0-indexed** Fenwick Tree for tracking cumulative values over a dynamic array. It is suitable for systems where frequent prefix sum queries and point updates are required, such as time-based accounting, queuing systems, or share tracking. ### Design Characteristics * `O(log n)` complexity for both updates and prefix sum queries. * Storage-efficient using a `mapping(uint256 => int256)`, instead of an array. * Only supports lengths that are exact powers of two (`2^k`), which simplifies internal logic and allows future extensions via `extend()`. ### Invariants and Constraints * The tree must be initialized with a power-of-two length > 0 via `initialize(...)`. * Index bounds are enforced – access beyond the current capacity reverts with `IndexOutOfBounds()`. * To support dynamic resizing, `extend()` can double the current tree length (up to a safe limit). * Use of negative values is supported in `modify(...)`, allowing decrement operations. ### Data Structures ```solidity theme={null} struct Tree { mapping(uint256 index => int256) _values; // Internal Fenwick Tree nodes. uint256 _length; // Capacity of the tree (must be power of two). } ``` ### Functions ### `initialize(Tree storage tree, uint256 length_)` Initializes the tree with the specified length. * Reverts with `InvalidLength()` if `length_ == 0` or not a power of two. * Only callable once (re-initialization is not allowed). ### `length(Tree storage tree) → uint256` Returns the current capacity of the tree. ### `extend(Tree storage tree)` Doubles the tree's capacity. * Preserves prefix sum structure. * Reverts with `InvalidLength()` on overflow. ### `modify(Tree storage tree, uint256 index, int256 value)` Increments or decrements the value at a given index by `value`. * Performs `tree[index] += value`. * Reverts if index is out of bounds. * No-op if `value == 0`. ### `get(Tree storage tree, uint256 index) → int256` Returns the **prefix sum** for the range `[0, index]`. * If `index >= length`, it is clamped to `length - 1`. ### `get(Tree storage tree, uint256 from, uint256 to) → int256` Returns the sum over the range `[from, to]` (inclusive). * Returns `0` if `from > to`. ### Internals ### `_modify(...)` Low-level implementation of Fenwick update using bitwise operations: * Updates `tree[index]` and propagates changes upward via `index |= index + 1`. ### `_get(...)` Assembly-optimized prefix sum computation: * Aggregates values by descending via `index := and(index, index + 1) - 1`. ### References * [CP Algorithms: Fenwick Tree](https://cp-algorithms.com/data_structures/fenwick.html) * [Wikipedia: Binary Indexed Tree](https://en.wikipedia.org/wiki/Fenwick_tree) # Libraries Source: https://docs.mellow.finance/core-vaults/architecture/libraries/index In this directory, you will find a detailed per-contract overview of the libraries used in Core Vaults, including the following: [FenwickTreeLibrary](/core-vaults/architecture/libraries/fenwicktreelibrary) [ShareManagerLibrary](/core-vaults/architecture/libraries/sharemanagerlibrary) [SlotLibrary](/core-vaults/architecture/libraries/slotlibrary) [TransferLibrary](/core-vaults/architecture/libraries/transferlibrary) # ShareManagerLibrary Source: https://docs.mellow.finance/core-vaults/architecture/libraries/sharemanagerlibrary This library helps pack multiple boolean flags and lockup durations into a compact `uint256` bitmask. It enables efficient storage and quick access to share manager configuration in vault systems. Designed for the `ShareManager` component to control: * Whether minting, burning, or transfers are paused * Whether deposit/transfer whitelists are active * How long global or user-specific lockups last All data is packed into a single `uint256` using bit-level encoding for optimal storage and gas efficiency. ### Bitmask Layout | Bit Range | Purpose | | - | - | | `[0]` | `hasMintPause` (bool) | | `[1]` | `hasBurnPause` (bool) | | `[2]` | `hasTransferPause` (bool) | | `[3]` | `hasWhitelist` (bool) | | `[4]` | `hasTransferWhitelist` (bool) | | `[5..36]` | `globalLockup` (uint32) | | `[37..68]` | `targetedLockup` (uint32) | ### Functions ### `hasMintPause(uint256 mask) → bool` Returns `true` if minting is paused (bit 0 is set). ### `hasBurnPause(uint256 mask) → bool` Returns `true` if burning is paused (bit 1 is set). ### `hasTransferPause(uint256 mask) → bool` Returns `true` if transfers are paused (bit 2 is set). ### `hasWhitelist(uint256 mask) → bool` Returns `true` if a deposit whitelist is enabled (bit 3 is set). ### `hasTransferWhitelist(uint256 mask) → bool` Returns `true` if a transfer whitelist is enabled (bit 4 is set). ### `getGlobalLockup(uint256 mask) → uint32` Returns the **global lockup duration** in seconds (timestamp), encoded in bits `[5..36]`. ### `getTargetedLockup(uint256 mask) → uint32` Returns the **targeted lockup duration** in seconds, encoded in bits `[37..68]`. ### `createMask(IShareManager.Flags calldata f) → uint256` Encodes the values in a `Flags` struct into a single bitmask: ```solidity theme={null} struct Flags { bool hasMintPause; bool hasBurnPause; bool hasTransferPause; bool hasWhitelist; bool hasTransferWhitelist; uint32 globalLockup; uint32 targetedLockup; } ``` # SlotLibrary Source: https://docs.mellow.finance/core-vaults/architecture/libraries/slotlibrary This library generates unique and collision-resistant storage slots for use in upgradeable Solidity contracts. It ensures that different modules or instances do not unintentionally overwrite each other's storage, even when used via proxy or delegate calls. ### Storage Slot Strategy * Based on EIP-7201 * Inputs include: * Contract name (`contractName`) * Human-readable name (`name`) * Version number (`version`) * Final slot: ```solidity theme={null} keccak256( abi.encode( uint256( keccak256( abi.encodePacked( "mellow.flexible-vaults.storage.", contractName, name, version ) ) ) - 1 ) ) & ~bytes32(uint256(0xff)); ``` This structure ensures: * **Namespacing:** Prevents overlap between different modules (`ShareModule`, `FeeManager`, etc.) * **Instance separation:** Multiple deployments with different names produce distinct slots * **Versioning:** Upgrades can cleanly migrate to new versions without collision ### Function ### `getSlot(string contractName, string name, uint256 version) → bytes32` **Description:** Computes a deterministic, collision-resistant storage slot for a contract module. **Parameters:** * `contractName`: Logical name of the module (e.g., `"ShareModule"`) * `name`: Instance identifier or label (e.g., `"Mellow"`) * `version`: Numeric version for versioned slot separation **Returns:** * A `bytes32` value representing the computed storage slot **Example:** ```solidity theme={null} bytes32 slot = SlotLibrary.getSlot("FeeManager", "Mellow", 1); ``` # TransferLibrary Source: https://docs.mellow.finance/core-vaults/architecture/libraries/transferlibrary This utility abstracts away the differences between transferring native ETH and ERC20 tokens by introducing a unified interface for both sending and receiving assets. It also standardizes how native ETH is represented onchain to simplify integration logic across different components. ### ETH Representation The constant `ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` ([\*\*EIP-7528](https://ethereum-magicians.org/t/eip-7528-eth-native-asset-address-convention/15989))\*\* is used as a sentinel value to distinguish native ETH from ERC20 tokens. ### Errors * `InvalidValue()`: Thrown when the contract expects a specific `msg.value` (for native ETH transfers) but receives a different amount. ### Constants * `ETH`: Reserved address used to represent native Ether. When passed to `sendAssets` or `receiveAssets`, the function will process ETH instead of calling token functions. ### Functions ### `sendAssets(address asset, address to, uint256 assets)` Sends the specified asset (`assets` amount) to the recipient `to`. * If `asset == ETH`, the function uses `Address.sendValue` to transfer native ETH. * If `asset` is an ERC20 token, it uses `IERC20.safeTransfer`. **Parameters:** * `asset`: Address of the asset to transfer. Should be `ETH` for native Ether or the ERC20 token address. * `to`: Address to send the asset to. * `assets`: Amount of the asset to transfer. **Reverts if:** ETH transfer fails or ERC20 transfer fails via `SafeERC20`. ### `receiveAssets(address asset, address from, uint256 assets)` Receives assets from the caller (or a third-party) into the current contract. * If `asset == ETH`, verifies that `msg.value == assets`. * If `asset` is an ERC20 token, calls `IERC20.safeTransferFrom` from `from` to the current contract. **Parameters:** * `asset`: Address of the asset to receive. Use `ETH` for native Ether or an ERC20 token address. * `from`: Address sending the ERC20 tokens (ignored for ETH). * `assets`: Expected amount of the asset to receive. **Reverts if:** * The contract receives an incorrect `msg.value` for ETH. * The ERC20 transfer fails. > Calling `receiveAssets(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, from, assets)` multiple times within a single function call will result in incorrect asset accounting. > > **DO NOT** use this function in scenarios like the following: ```solidity theme={null} function func() external payable { TransferLibrary.receiveAssets(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, msg.sender, 1 ether); TransferLibrary.receiveAssets(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, msg.sender, 1 ether); ... } ``` # BasicShareManager Source: https://docs.mellow.finance/core-vaults/architecture/managers/basicsharemanager ### Overview `BasicShareManager` is a concrete implementation of the abstract `ShareManager`, designed to provide native ERC20-style share accounting within a modular vault system. It handles minting, burning, and tracking balances of vault shares directly through a local ERC20-compatible storage layout, without exposing standard ERC20 interfaces. This contract is intended for setups where shares are not tokenized onchain as ERC20s but are still tracked internally using the ERC20Upgradeable storage schema. ### Key Features * Uses `ShareManager` for permissioning, allocation, and whitelisting logic. * Maintains balances and total supply using `ERC20Upgradeable.ERC20Storage`. * Internal mint/burn logic emits `IERC20.Transfer` events (for transparency or compatibility). * Fully decoupled from standard `ERC20` interface – share transfers are governed by vault queues and mint/burn logic only. ### Storage ERC20-style balances and supply are stored at a fixed storage slot allowing for migrations BasicShareManager ↔ TokenizedShareManager: ```solidity theme={null} bytes32 private constant ERC20StorageLocation = 0x52c6...ce00; ``` ### Initialization ```solidity theme={null} function initialize(bytes calldata data) external initializer ``` * Expects a single `bytes32 whitelistMerkleRoot` (used by `ShareManager`). ### View Functions * `activeShares()`: Returns `_totalSupply` from ERC20 storage. * `activeSharesOf(account)`: Returns balance of `account`. ### Internal Logic ### `_mintShares(address, uint256)` * Checks if minting is allowed via `updateChecks`. * Increments total supply and receiver's balance. * Emits `IERC20.Transfer(address(0), account, value)`. Reverts if: * `account == address(0)` * Minting is paused or restricted by lockup, whitelist, or blacklist ### `_burnShares(address, uint256)` * Checks if burning is allowed via `updateChecks`. * Decreases sender's balance and total supply. * Emits `IERC20.Transfer(account, address(0), value)`. Reverts if: * `account == address(0)` * `value > account balance` * Burning is paused or blocked ### Design Notes * This module deliberately avoids exposing the ERC20 interface, preventing any unintended external transfers or integrations. * It is intended for internal share accounting within vault systems, where shares are tracked but not tokenized onchain. * All permissioning logic, including minting, burning, whitelisting, and lockup enforcement, is delegated to the inherited `ShareManager`. * This implementation is ideal when the vault owner requires non-transferable shares for internal logic, without compliance to ERC20 or ERC4626 standards. * It is **not appropriate** for setups where shares must be externally transferable, interoperable with third-party protocols, or conform to token standards. # BurnableTokenizedShareManager Source: https://docs.mellow.finance/core-vaults/architecture/managers/burnabletokenizedsharemanager ### Overview `BurnableTokenizedShareManager` extends [`TokenizedShareManager`](/core-vaults/architecture/managers/tokenizedsharemanager) with two ERC20 burn functions, in the style of OpenZeppelin's `ERC20Burnable`. Holders can destroy their own vault shares, or shares they have an allowance for, without going through a redeem queue. Everything else, including minting, locking, whitelists, lockups and transfer rules, is inherited unchanged from `TokenizedShareManager` and `ShareManager`. In the standard deployment, the share manager factory registers it as version `2`, after `TokenizedShareManager` (version `0`) and `BasicShareManager` (version `1`). ### Functions ### `burn(uint256 value)` Burns `value` shares from the caller's balance. ### `burnFrom(address account, uint256 value)` Spends `value` of the caller's ERC20 allowance from `account`, then burns `value` shares from `account`. Both functions are public and need no vault role. They are separate from the queue-only `burn(address account, uint256 value)` inherited from `ShareManager`, which redeem queues use. ### Checks on burn Burns go through the token's `_update` hook, so the usual `ShareManager.updateChecks(from, address(0))` rules apply: * Reverts with `GlobalLockupNotExpired` while the global lockup is active * Reverts with `Blacklisted` if the holder is blacklisted * Reverts with `BurnPaused` if the burn pause flag is set Before the burn, `TokenizedShareManager` claims any shares the holder has waiting in deposit queues, as it does for every balance change. ### Effect on the vault Burning shares with these functions does not return any assets to the holder. The vault's assets stay in place while the share supply shrinks, so the value of the burned shares is shared among the remaining holders from the next oracle report. These public burns do not emit the `ShareManager` `Burn` event. Indexers see a standard ERC20 `Transfer(account, address(0), value)` event. ### When to use it Use `BurnableTokenizedShareManager` when shares must be destroyable outside the redemption flow. Examples are buyback-and-burn programs, or integrations that expect an `ERC20Burnable`-compatible token. If you do not need public burning, use `TokenizedShareManager`. # FeeManager Source: https://docs.mellow.finance/core-vaults/architecture/managers/feemanager **Modular, upgradeable fee management contract for vaults.** The `FeeManager` is responsible for managing and calculating various fee types in a vault system, including deposit, redemption, performance, and protocol fees. It uses a flexible architecture with deterministic storage slots (via `SlotLibrary`) and supports per-vault configurations. ### Key Responsibilities * Configures and stores global fee settings (in D6 precision). * Tracks vault-specific state (base asset, min price, timestamp). * Computes: * **Deposit Fee**: Fee charged on asset deposit. * **Redeem Fee**: Fee charged on share redemption. * **Performance Fee**: Fee based on drop in price (`assets * price = shares`) * **Protocol Fee**: Time-based fee accrued per vault. * Provides administrative controls to update fee settings and vault metadata. ### Storage Uses an isolated storage slot per deployment instance, computed deterministically using `SlotLibrary.getSlot("FeeManager", name, version)`, ensuring safety and upgradability. Each vault is associated with: * `baseAsset`: Reference token used for performance fee calculation. * `minPriceD18`: Minimum price recorded (used for performance fee calculation). * `timestamps`: Last update timestamp for time-based fee accrual. ### Fee Calculation Logic ### `calculateDepositFee(uint256 shares) → uint256` Computes a linear fee as `shares * depositFeeD6 / 1e6`. ### `calculateRedeemFee(uint256 shares) → uint256` Computes a linear fee as `shares * redeemFeeD6 / 1e6`. ### `calculateFee(...) → uint256 shares` Calculates the total fee to be charged based on: * Performance: If current `priceD18` below `minPriceD18`, applies `performanceFeeD6` as `(minPriceD18 - priceD18) * performanceFeeD6 * totalShares / (priceD18 * 1e6)`. * Protocol: Time-weighted fee based on `block.timestamp - timestamps[vault]`, computed as `totalShares * protocolFee * (block.timestamp - timestamps[vault]) / (365 * 24 * 3600 * 1e6)` All fees are paid in **shares** of the vault (not assets). ### Access Control * Only the `owner` (defined during initialization) can modify fee parameters or vault configurations. * Calls to `initialize(...)` must come from the factory and include all required setup parameters. ### Events * `Initialized(bytes data)`: Emitted after initialization. * `SetFeeRecipient(address feeRecipient)`: On recipient update. * `SetFees(...)`: On any fee change. * `SetBaseAsset(...)`: When base asset is configured per vault. * `UpdateState(...)`: On state update used for protocol/performance fees. ### Errors * `ZeroAddress()`: Thrown if an address input is zero. * `InvalidFees(...)`: When the combined fee rate exceeds 100% (`1e6` D6). * `BaseAssetAlreadySet(...)`: Prevents base asset override if already set. ### Lifecycle 1. **Constructor**: Sets storage slot based on name/version. 2. **Initialization** (via `initialize(bytes)`): Sets owner, recipient, and fees. 3. **Fee Updates**: Admin can update recipient and fee parameters. 4. **Vault Hooks**: * `updateState(asset, price)`: Vaults call this to refresh timestamp and min price. * `setBaseAsset`: Called once per vault to register its performance reference token. # Managers Source: https://docs.mellow.finance/core-vaults/architecture/managers/index In this directory, you will find a detailed per-contract overview of the "Managers" contract category, including the following contracts: [FeeManager](/core-vaults/architecture/managers/feemanager) [RiskManager](/core-vaults/architecture/managers/riskmanager) [ShareManager](/core-vaults/architecture/managers/sharemanager) [BasicShareManager](/core-vaults/architecture/managers/basicsharemanager) [TokenizedShareManager](/core-vaults/architecture/managers/tokenizedsharemanager) [BurnableTokenizedShareManager](/core-vaults/architecture/managers/burnabletokenizedsharemanager) # RiskManager Source: https://docs.mellow.finance/core-vaults/architecture/managers/riskmanager **Onchain risk control and allocation policy manager for modular vaults.** The `RiskManager` contract defines and enforces asset deposit limits across a `Vault` and its associated `Subvaults`. It maintains internal accounting of balances, limits and allowed subvault assets. ### Purpose This contract is a centralized module responsible for: * Defining and enforcing **deposit limits** at vault and subvault levels. * Allowlisting or disallowing **specific assets** per subvault. * Tracking **pending balances** (deposits/withdrawals that are not yet finalized). * Validating risk assumptions through **oracle price reports**. ### Core Concepts * **Vault Limit**: Global cap across all assets managed by the vault (in shares). * **Subvault Limit**: Individual cap per subvault, enforced independently (in shares). * **Allowed Assets**: Only explicitly allowlisted assets are permitted in a given subvault. * **Pending Assets**: Temporarily tracked assets, e.g., during deposit queueing * **Shares Conversion**: All balances are internally tracked in shares, calculated using latest report in the `Oracle` contract. ### Storage Slot Utilizes a deterministic storage slot computed via `SlotLibrary.getSlot("RiskManager", name, version)` to ensure safe upgrades and modular deployment. ### Roles and Permissions The contract uses fine-grained access roles: * `SET_VAULT_LIMIT_ROLE`: Can modify global vault capacity. * `SET_SUBVAULT_LIMIT_ROLE`: Can change limits on individual subvaults. * `ALLOW_SUBVAULT_ASSETS_ROLE`: Can whitelist assets for specific subvaults. * `DISALLOW_SUBVAULT_ASSETS_ROLE`: Can revoke asset approval from subvaults. * `MODIFY_PENDING_ASSETS_ROLE`: Can manipulate pending balance delta. * `MODIFY_VAULT_BALANCE_ROLE`: Can update the vault's live balance. * `MODIFY_SUBVAULT_BALANCE_ROLE`: Can update a subvault's internal balance. Roles are verified via the vault's ACL module (`IACLModule`) or allowed queues (`IShareModule`). ### Key Methods ### View * `vault()`: Returns the vault address. * `vaultState()`: Returns the global vault state (limit, balance). * `pendingBalance()`: Returns the pending share balance across all assets and deposit queues. * `subvaultState(address)`: Returns per-subvault state. * `pendingAssets(address)`: Returns currently pending asset amount. * `pendingShares(address)`: Returns share-equivalent of pending assets. * `allowedAssets(address)`: Count of allowed assets in subvault. * `allowedAssetAt(address, index)`: Indexed lookup of allowed asset. * `isAllowedAsset(address, asset)`: Checks asset permission for subvault. * `convertToShares(asset, value)`: Converts amount to share units using oracle. * `maxDeposit(subvault, asset)`: Calculates max deposit amount given limits and prices. ### Mutable * `initialize(bytes data)`: Initializes vault-wide limit. * `setVault(address)`: Assigns the vault address (one-time only). * `setVaultLimit(int256 limit)`: Updates vault's global limit. * `setSubvaultLimit(address, int256)`: Updates limit for a specific subvault. * `allowSubvaultAssets(address, address[])`: Adds assets to subvault's allowlist. * `disallowSubvaultAssets(address, address[])`: Removes assets from allowlist. * `modifyPendingAssets(address, int256)`: Adjusts pending assets and updates internal shares. * `modifyVaultBalance(address, int256)`: Applies a delta to vault's current balance (with limit checks). * `modifySubvaultBalance(address, asset, int256)`: Same as above, but scoped to a specific subvault. ### Internal Mechanics ### Conversion to Shares Conversion is done with oracle price data, where: ```solidity theme={null} shares = (value * priceD18) / 1e18 ``` ### Assumptions The system assumes that: * The vault and its subvaults operate exclusively with **correlated assets**, and * Protocol-level delegations performed by the curator **do not introduce extreme APR variance or significant principal loss**. Given this, all vault- and subvault-level limits are treated as **approximate** and are computed using the most recent oracle report available **at the time of the state update** (e.g., on pull/push or deposit/redeem operations). If actual balances deviate significantly from the stored `balance` values due to oracle drift, delayed execution, or protocol-side changes, a **trusted actor** can apply a 'corrections' to mitigate the difference: * `modifyVaultBalance` for the Vault, or * `modifySubvaultBalance` for individual Subvaults. Since the system is expected to hold only correlated assets, such manual adjustments are assumed to be **rare** under normal operating conditions. # ShareManager Source: https://docs.mellow.finance/core-vaults/architecture/managers/sharemanager ### Overview The `ShareManager` is an abstract upgradeable contract responsible for managing vault share issuance, allocation, whitelisting, permissions, and lockups in a modular vault system. It relies on a compact bitmask (`flags`) for enabling/disabling features and supports configurable per-account permissions. ### Key Responsibilities * Tracking total and active share supply * Managing global and per-account lockups * Verifying whitelist and transfer permissions * Enforcing mint/burn/transfer pauses * Allocating and claiming shares through queues * Emitting granular control events on state transitions ### Storage Storage is accessed via a deterministic slot generated by `SlotLibrary.getSlot("ShareManager", name, version)`. ```solidity theme={null} struct ShareManagerStorage { address vault; uint256 flags; // Encodes permissions and lockup durations uint256 allocatedShares; bytes32 whitelistMerkleRoot; mapping(address => AccountInfo) accounts; } ``` ### Permission System Controlled through roles defined as: * `SET_FLAGS_ROLE`: Allows changing global flags (e.g., mint pause, whitelist enforcement). * `SET_ACCOUNT_INFO_ROLE`: Grants permission to set per-account configuration. * `SET_WHITELIST_MERKLE_ROOT_ROLE`: Grants permission to set new whitelist merkle root. * All share-related actions are guarded via: * `onlyQueue()` * `onlyVaultOrQueue()` * `onlyRole(...)` ### View Functions * `vault()`: Returns the vault address. * `sharesOf(account)`: Total shares (active + claimable). * `activeSharesOf(account)`: Abstract; must be implemented by child. * `claimableSharesOf(account)`: Reads from the `IShareModule`. * `totalShares()`: `allocatedShares + activeShares()` * `accounts(account)`: Returns `AccountInfo` struct (deposit/transfer flags, lockups, blacklisting). * `flags()`: Decoded bitmask as `Flags` struct. * `whitelistMerkleRoot()`: Current root used for off-chain whitelist proof verification. * `isDepositorWhitelisted(account, proof)`: Verifies Merkle proof or checks local permission flags. * `updateChecks(from, to)`: Reverts on violations (paused actions, lockups, blacklisting, etc.). ### Mutable Functions * `setVault(...)`: One-time vault initialization. * `setFlags(...)`: Updates global bitmask configuration. * `setWhitelistMerkleRoot(…)`: Updates whitelist merkle root. * `setAccountInfo(...)`: Sets access rights for an individual address. * `claimShares(...)`: Claims shares from the vault's `IShareModule`. * `allocateShares(...)`: Allocates shares for future minting (only callable by a queue). * `mintAllocatedShares(...)`: Mints shares from allocated pool to user. * `mint(...)`: Mints shares to a user with optional lockup. * `burn(...)`: Burns a user's shares (only queue). * `__ShareManager_init(...)`: Sets Merkle root at construction or upgrade. ### Internal Hooks (Implemented by Child) ```solidity theme={null} function _mintShares(address account, uint256 value) internal virtual; function _burnShares(address account, uint256 value) internal virtual; ``` These abstract functions allow concrete implementations to define how share balances are recorded or tokenized. ### Bitmask-Controlled Features Controlled via `ShareManagerFlagLibrary`: * `hasMintPause` * `hasBurnPause` * `hasTransferPause` * `hasWhitelist` * `hasTransferWhitelist` * `globalLockup` * `targetedLockup` Lockups are enforced in `updateChecks`. # TokenizedShareManager Source: https://docs.mellow.finance/core-vaults/architecture/managers/tokenizedsharemanager ### Overview * This module extends `ShareManager` and `ERC20Upgradeable`, making vault shares externally transferable and fully compliant with the ERC20 standard. * It is intended for vaults that require tokenized shares usable across external protocols, wallets, or DeFi integrations. * Core share logic (minting, burning, whitelisting, lockups) is delegated to the inherited `ShareManager`, preserving consistent permission enforcement. * Whitelist enforcement, lockup mechanics, and share claim logic are integrated into the overridden `_update` hook, which ensures all token transfers pass necessary checks and call `claimShares` for non-zero actors. * Suitable for use cases where share liquidity, composability, or token standard compatibility (e.g., ERC20, ERC4626 wrappers) is required. # ACLModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/aclmodule ### Overview Abstract module integrating role-based access control via `MellowACL`, providing permission management functionality. ## Internal Functions ### `__ACLModule_init` ```solidity theme={null} function __ACLModule_init(address admin_) internal onlyInitializing ``` ### Description Initializes the module with an admin address by assigning the `DEFAULT_ADMIN_ROLE`. This sets up the foundational RBAC structure. ### Parameters * `admin_`: Address to be granted the `DEFAULT_ADMIN_ROLE`. ### Requirements * `admin_` must not be the zero address. * Callable only during initialization (`onlyInitializing`). # BaseModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/basemodule ## Overview `BaseModule` is an abstract contract that acts as a foundational layer for modules within the system. It integrates shared logic such as initializer protection, reentrancy guard, IERC721Receiver compliance and low level storage access. This module is intended to be inherited and extended by other functional modules. ## Constructor ```solidity theme={null} constructor() { _disableInitializers(); } ``` Prevents the contract from being initialized outside of proxy context. Ensures secure upgradeable deployments. ## Public & External Functions ### `getStorageAt(bytes32 slot)` ```solidity theme={null} function getStorageAt(bytes32 slot) external pure returns (StorageSlot.Bytes32Slot memory) ``` Returns a reference to a custom storage slot. Enables advanced access to shared storage across upgradeable modules using the `StorageSlot` pattern. **Parameters:** * `slot` - The `bytes32` identifier of the storage slot. **Returns:** * A `StorageSlot.Bytes32Slot` struct pointing to the slot. ### `onERC721Received(...)` ```solidity theme={null} function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) ``` ERC721 receiver hook implementation to support safe transfers of NFTs to the module. Returns the selector as required by `IERC721Receiver`. **Returns:** * `IERC721Receiver.onERC721Received.selector` - confirms compliance. ### `receive()` ```solidity theme={null} receive() external payable {} ``` Allows the contract to receive native ETH transfers. This is typically used for vaults handling native tokens directly. ## Internal Functions ### `__BaseModule_init()` ```solidity theme={null} function __BaseModule_init() internal onlyInitializing ``` Initializes internal dependencies and base upgradeable components. Should be called from derived contract initializers. **Side Effects:** * Calls `__ReentrancyGuard_init()` to initialize reentrancy protection. # CallModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/callmodule ### Overview Abstract contract extending `VerifierModule`, implementing low-level contract calls with verification via a pluggable verifier. ### `call` ```solidity theme={null} function call( address where, uint256 value, bytes calldata data, IVerifier.VerificationPayload calldata payload ) external nonReentrant returns (bytes memory response) ``` ### Description Executes a low-level call to a target contract after validating the call parameters through an external `Verifier` contract. The verification logic is determined by the verification type specified in the `Verifier` contract. For details on available verification types, refer to the [Verifier specification](/core-vaults/architecture/permissions/verifier). ### Parameters * `where`: The address of the target contract. * `value`: The ETH value to send along with the call. * `data`: Calldata to pass to the target contract. * `payload`: Encoded verification payload used to authorize the call. ### Returns * `response`: The raw returned data from the target contract call. ### Requirements * All the provided parameters must be externally verified via `verifier().verifyCall`. * Reentrancy is prevented via `nonReentrant` modifier. # Modules Source: https://docs.mellow.finance/core-vaults/architecture/modules/index In this directory, you will find a detailed per-contract overview of the "Modules" contract category, including the following modules: [VerifierModule](/core-vaults/architecture/modules/verifiermodule) [ACLModule](/core-vaults/architecture/modules/aclmodule) [CallModule](/core-vaults/architecture/modules/callmodule) [ShareModule](/core-vaults/architecture/modules/sharemodule) [VaultModule](/core-vaults/architecture/modules/vaultmodule) [SubvaultModule](/core-vaults/architecture/modules/subvaultmodule) # ShareModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/sharemodule ### Purpose `ShareModule` is a core module responsible for managing user interactions with a vault through structured deposit and redeem queues. It provides governance over queue creation, hook configurations, oracle-driven settlement, and fee accounting. ### Key Responsibilities * Tracks and validates all deposit/redeem queue operations. * Coordinates price reporting with oracle. * Facilitates dynamic queue configuration and lifecycle. * Integrates hooks for deposit/redeem processing customization. * Central hub for protocol and performance fee minting, share claim logic and report handling. ### Roles * `SET_HOOK_ROLE`: Grants the ability to modify per-queue and default hook addresses. * `CREATE_QUEUE_ROLE`: Allows creation of new deposit/redeem queues. * `SET_QUEUE_STATUS_ROLE`: Permits pausing/unpausing individual queues. * `SET_QUEUE_LIMIT_ROLE`: Enables setting the max number of total queues. * `REMOVE_QUEUE_ROLE`: Allows safe removal of queues with `canBeRemoved()` check. ### Core Storage Layout (`ShareModuleStorage`) * `shareManager`: Reference to contract handling share minting/burning. * `feeManager`: Reference to contract that calculates fees and stores fee-related data. * `oracle`: Oracle contract providing price data and asset support status. * `defaultDepositHook` / `defaultRedeemHook`: Global fallback hooks used by default in case custom hooks are not defined. * `customHooks`: Per-queue override for custom hook logic. * `queueCount`: Total existing queues. * `queueLimit`: Global max limit for queues. * `isDepositQueue`: Distinguishes deposit queues from redeem ones. * `isPausedQueue`: Tracks paused queues. * `queues`: Asset → queues mapping. * `assets`: Registry of all assets with registered queues. ### View Functions * `shareManager()`: Returns the `IShareManager` instance. * `feeManager()`: Returns the `IFeeManager` instance. * `oracle()`: Returns the `IOracle` instance. * `depositQueueFactory()` / `redeemQueueFactory()`: Queue factory contracts. * `queueLimit()`: Max allowed queues. * `claimableSharesOf(account)`: Sum of claimable shares across all deposit queues for the `account`. * `getLiquidAssets()`: Called by redeem queues to determine liquidity available for handling redemptions. Returns the redeem hook's `getLiquidAssets(asset)`, or the vault's own balance if the queue has no hook. Reverts with `Forbidden` if the caller is not a registered redeem queue. * `defaultDepositHook()` / `defaultRedeemHook()`: Global default hooks. * `getHook(queue)`: Resolves hook for queue (custom or default fallback as a fallback). * Asset/Queue helpers: * `getAssetCount()`, `assetAt(index)`, `hasAsset(asset)` * `hasQueue(queue)`, `isDepositQueue(queue)`, `isPausedQueue(queue)` * `getQueueCount()` / `getQueueCount(asset)` * `queueAt(asset, index)` ### Mutable Functions * `claimShares(account)`: Claims all claimable shares from deposit queues for the specific account. * `callHook(assets)`: Calls the queue's associated hook. Transfers assets to the queue if redeem. * `setCustomHook(queue, hook)`: Assigns per-queue hook. * `setDefaultDepositHook(hook)` / `setDefaultRedeemHook(hook)`: Sets global hooks. * `setQueueLimit(limit)`: Updates global queue cap. * `setQueueStatus(queue, isPaused)`: Pauses/unpauses a queue. * `createQueue(version, isDeposit, owner, asset, data)`: Deploys new queue for asset. `version` selects the queue implementation in the deposit or redeem queue factory. In the standard deployment, `0` is the async queue, `1` the signature queue and `2` the sync queue. See [SyncQueue](/core-vaults/architecture/queues/syncqueue#creating-a-sync-queue). * `removeQueue(queue)`: Removes a queue that passed `canBeRemoved()`. * `handleReport(asset, priceD18, depositTimestamp, redeemTimestamp)`: * Called by the oracle. * Distributes protocol fees. * Propagates price report to all queues and calls hooks. ### Events * `SharesClaimed(account)` * `CustomHookSet(queue, hook)` * `QueueCreated(queue, asset, isDepositQueue)` * `QueueRemoved(queue, asset)` * `HookCalled(queue, asset, assets, hook)` * `QueueLimitSet(limit)` * `SetQueueStatus(queue, isPaused)` * `DefaultHookSet(hook, isDepositHook)` * `ReportHandled(asset, priceD18, depositTimestamp, redeemTimestamp, fees)` # SubvaultModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/subvaultmodule ### Purpose The `SubvaultModule` represents an isolated child vault within a modular vault system. It is tightly controlled by its parent vault (typically a `VaultModule`) and is responsible for securely holding and releasing assets upon authenticated requests. ### Responsibilities * Store and isolate a portion of vault assets * Allow trusted actor (curator) to delegate liquidity from the Subvault to external protocol based on the `Verifier` setup for this specific subvault * Respond to `pullAssets` calls from the parent vault only ### Storage Layout (`SubvaultModuleStorage`) ```solidity theme={null} struct SubvaultModuleStorage { address vault; } ``` * `vault`: Address of the root vault that controls this subvault. Only this address can request asset withdrawals. The layout is stored in a deterministic custom slot derived using: ```solidity theme={null} SlotLibrary.getSlot("SubvaultModule", name_, version_) ``` ### View Functions ### `vault() → address` Returns the address of the parent vault that instantiated this subvault. ### Mutable Functions ### `pullAssets(asset: address, value: uint256)` Allows the parent vault to withdraw a specified amount of an asset. * **Access Control**: Can only be called by the `vault()` address * **Reverts**: With `NotVault()` if the caller is not the vault * **Transfer Behavior**: Uses `TransferLibrary.sendAssets()` to forward tokens or native ETH to the `Vault.sol` address * **Emits**: `AssetsPulled(asset, vault, value)` ### Internal Initialization ### `__SubvaultModule_init(address vault_)` Internal setup method to be called during construction or proxy initialization. ### Events ### `event AssetsPulled(address indexed asset, address indexed to, uint256 value)` Triggered when assets are withdrawn by the parent vault. * `asset`: Address of the ERC20 token or native ETH * `to`: Always equals the `vault()` address * `value`: Amount of the asset transferred ### Error Handling * **`NotVault()`**: Raised when a non-vault caller attempts to call `pullAssets()` # VaultModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/vaultmodule ### Purpose `VaultModule` is a core component of the modular vault architecture. It manages liquidity routing between the [`Vault`](/core-vaults/architecture/vaults/vault) and its connected [`Subvaults`](/core-vaults/architecture/vaults/subvault), enabling flexible strategy composition and modular upgrades. It supports hot-swapping of subvault contracts and enforces contract-level control over asset movement. ### Responsibilities * Orchestrate liquidity push/pull operations between the vault and subvaults * Create, disconnect, and reconnect subvaults * Verify creations, removals and reconnections using external `Factory` contracts and local state * Track and update risk exposure via `RiskManager` ### Roles * `CREATE_SUBVAULT_ROLE`: Allows creation of new subvaults * `DISCONNECT_SUBVAULT_ROLE`: Allows disconnection of active subvaults * `RECONNECT_SUBVAULT_ROLE`: Allows reattachment of disconnected or new properly configured subvaults * `PULL_LIQUIDITY_ROLE`: Grants permission to pull assets from subvaults * `PUSH_LIQUIDITY_ROLE`: Grants permission to send assets to subvaults ### Storage Layout (`VaultModuleStorage`) ```solidity theme={null} struct VaultModuleStorage { address riskManager; EnumerableSet.AddressSet subvaults; } ``` * `riskManager`: Module used to track and limit exposure per asset/subvault * `subvaults`: Enumerable set of currently connected subvaults ### View Functions * `subvaultFactory()`: Returns `IFactory` used to deploy and check deployed subvaults * `verifierFactory()`: Returns `IFactory` used to deploy and check deployed verifiers * `subvaults()`: Returns the total number of connected subvaults * `subvaultAt(index)`: Returns the subvault address at a specific index * `hasSubvault(address)`: Checks if a given address is an active subvault * `riskManager()`: Returns the address of the risk manager ### Mutable Functions ### Subvault Management * `createSubvault(version, owner, verifier)`: * Deploys a new subvault via the `subvaultFactory` * Links it to the provided `verifier` * Adds it to the vault's subvault list * Emits `SubvaultCreated` * `disconnectSubvault(subvault)`: * Removes a subvault from the vault registry * Emits `SubvaultDisconnected` * Reverts with `NotConnected` if not already linked * `reconnectSubvault(subvault)`: * Re-adds a subvault to the vault registry * Validates via `subvaultFactory` and `verifierFactory` * Emits `SubvaultReconnected` * Reverts with `InvalidSubvault`, `NotEntity`, or `AlreadyConnected` if checks fail ### Liquidity Movement * `pushAssets(subvault, asset, value)`: * Transfers assets from vault to subvault * Updates internal risk manager state (adds exposure) * Emits `AssetsPushed` * `pullAssets(subvault, asset, value)`: * Retrieves assets from a subvault * Updates internal risk manager state (reduces exposure) * Emits `AssetsPulled` ### Internal Liquidity Hooks These can only be invoked by the vault itself (via hooks): * `hookPushAssets(subvault, asset, value)` * `hookPullAssets(subvault, asset, value)` ### Error Conditions * `AlreadyConnected(subvault)`: When attempting to reconnect an already connected subvault * `NotConnected(subvault)`: When attempting to disconnect a subvault that isn't connected * `NotEntity(address)`: Provided contract is not a valid `IFactory`deployed entity * `InvalidSubvault(address)`: Subvault fails verification (incorrect `subvault.vault()` address) * `ZeroAddress()`: Passed `RiskManager` address is zero (used in `__VaultModule_init`) * `Forbidden()`: Caller is not authorized (used in internal checks) ### Events * `SubvaultCreated(subvault, version, owner, verifier)` * `SubvaultDisconnected(subvault)` * `SubvaultReconnected(subvault, verifier)` * `AssetsPulled(asset, subvault, value)` * `AssetsPushed(asset, subvault, value)` ### Security Considerations * All critical functions gated by role-based ACL * Uses factory-verified deployments for submodules * Internal state (risk exposure) updated on every asset movement * Only the vault contract itself may invoke `hook*` liquidity functions ### Initialization ```solidity theme={null} function __VaultModule_init(address riskManager_) internal onlyInitializing ``` * Sets the `riskManager` address (must be non-zero) * Should be invoked during deployment or upgrade setup # VerifierModule Source: https://docs.mellow.finance/core-vaults/architecture/modules/verifiermodule ## Overview `VerifierModule` is an abstract extension of `BaseModule` designed to provide standardized access to a `Verifier` contract. It manages internal storage using a deterministic slot derived via `SlotLibrary`, supporting secure modular composition across multiple vault systems. ## Constructor ```solidity theme={null} constructor(string memory name_, uint256 version_) ``` Computes and stores the custom storage slot used for verifier configuration based on a unique `(name_, version_)` pair. **Parameters:** * `name_` - Unique identifier used to namespace the storage slot. * `version_` - Version number used for slot derivation. ## Public & External Functions ### `verifier()` ```solidity theme={null} function verifier() public view returns (IVerifier) ``` Returns the address of the configured `Verifier` contract. It is retrieved from internal storage using a fixed slot. **Returns:** * `IVerifier` - The verifier contract associated with the module. ## Internal Functions ### `__VerifierModule_init(address verifier_)` ```solidity theme={null} function __VerifierModule_init(address verifier_) internal onlyInitializing ``` Initializes the verifier module with the given verifier contract address. Validates non-zero address to prevent misconfiguration. **Parameters:** * `verifier_` - Address of the verifier contract. **Reverts:** * `ZeroAddress()` if verifier address is zero. ## Private Functions ### `_verifierModuleStorage()` ```solidity theme={null} function _verifierModuleStorage() private view returns (VerifierModuleStorage storage) ``` Internal function to access the `VerifierModuleStorage` struct using the precomputed custom slot. Utilizes inline assembly for direct storage access. **Returns:** * `VerifierModuleStorage` - Storage struct holding verifier address. # Oracle Source: https://docs.mellow.finance/core-vaults/architecture/oracle #### Overview The `Oracle` contract is responsible for secure and configurable **price reporting** for supported assets. It is tightly coupled with a vault module (implementing `IShareModule`) and provides **price validation**, **deviation tracking**, and **rate-limited report submission**. It enforces strong assumptions around **data integrity**, **report timing**, and **trust minimization** through roles and deviation thresholds. This oracle ensures consistent pricing across all queue, share, and vault-related computations. #### Key Responsibilities * **Report Submission**: Allows trusted accounts to submit price updates * **Deviation Analysis**: Compares new prices against the last report for suspicious behavior * **Timestamp-based Rate Limiting**: Prevents frequent or premature reports * **Supported assets**: Controls which tokens are supported by the oracle * **Oracle Price Validation**: Used by other modules (e.g., `SignatureQueue`) to verify incoming prices #### Roles | Role | Description | | - | - | | `SUBMIT_REPORTS_ROLE` | Permission to submit regular price reports | | `ACCEPT_REPORT_ROLE` | Permission to accept suspicious reports | | `SET_SECURITY_PARAMS_ROLE` | Can modify validation rules and intervals | | `ADD_SUPPORTED_ASSETS_ROLE` | Can whitelist new assets for reporting | | `REMOVE_SUPPORTED_ASSETS_ROLE` | Can remove assets and delete associated state | #### **`SecurityParameters`** ```solidity theme={null} struct SecurityParams { uint224 maxAbsoluteDeviation; uint224 suspiciousAbsoluteDeviation; uint64 maxRelativeDeviationD18; uint64 suspiciousRelativeDeviationD18; uint32 timeout; uint32 depositInterval; uint32 redeemInterval; } ``` * **Absolute Deviation**: Hard limits on price delta in price units * **Relative Deviation**: Tolerance as a percentage (e.g., 5% = 0.05e18) * **Timeout**: Minimum time between valid reports (ignored if the previous report is suspicious) * **depositInterval**: Minimum age required for a deposit to be processed * **redeemInterval**: Same, but for redemptions #### **`Reports`** ```solidity theme={null} struct Report { address asset; uint224 priceD18; } struct DetailedReport { uint224 priceD18; uint32 timestamp; bool isSuspicious; } ``` Used to validate asset prices and coordinate cross-queue processing. #### Key Functions #### View | Function | Description | | - | - | | `vault()` | Returns the linked vault (must implement `IShareModule`) | | `securityParams()` | Current oracle thresholds and intervals | | `supportedAssets()` | Count of whitelisted tokens | | `supportedAssetAt(index)` | Token at a given index | | `isSupportedAsset(address)` | Whether an asset is valid for reporting | | `getReport(asset)` | Returns last report (price, timestamp, suspicious flag) | | `validatePrice(priceD18, asset)` | Validates a given price against the current report and security params | #### Mutable | Function | Description | | - | - | | `initialize(params)` | Initializes with assets and security settings | | `setVault(vault)` | Registers the vault for report consumption | | `submitReports(reports[])` | Batch-submits prices for multiple assets | | `acceptReport(asset, price, timestamp)` | Marks a previously suspicious report as trusted | | `setSecurityParams(params)` | Updates thresholds and timing rules | | `addSupportedAssets(assets[])` | Adds tokens to the supported set | | `removeSupportedAssets(assets[])` | Removes tokens and clears their reports | #### Reporting Logic When calling `submitReports(...)`: 1. Each asset is checked for support 2. The previous report is evaluated: * If `timeout` has not passed, and the report is not suspicious → **revert** `TooEarly` 3. Price is compared against previous: * Too far off → **revert** `InvalidPrice` * Moderately off → flagged `isSuspicious` 4. If the report is accepted: * Triggers `vault.handleReport(...)` with adjusted deposit and redeem timestamps * Emits `ReportsSubmitted` #### Validation Logic Prices are validated by: * Calculating **absolute deviation** * Calculating **relative deviation** * Comparing against `max` and `suspicious` thresholds A price is: * **Rejected** if either deviation exceeds max * **Accepted but suspicious** if above suspicious threshold * **Accepted as normal** if within all limits Used by: * `SignatureDepositQueue`, `SignatureRedeemQueue`, `DepositQueue` and `RedeemQueue` contracts * `SyncDepositQueue` and `SyncRedeemQueue`, which price instant deposits and redemptions with `getReport(asset)` * Vault's limit accounting (RiskManager) #### Events | Event | Purpose | | - | - | | `ReportsSubmitted(Report[])` | Emitted when new prices are posted | | `ReportAccepted(asset, price, timestamp)` | Suspicious report accepted | | `SecurityParamsSet(params)` | Oracle thresholds changed | | `SupportedAssetsAdded(addresses[])` | New tokens added | | `SupportedAssetsRemoved(addresses[])` | Tokens delisted | | `SetVault(address)` | Vault set | #### Security Considerations * Only trusted roles can push prices * Suspicious reports cannot be accepted without explicit approval * No pricing logic oracles trust external feeds – price validation is local * Prevents manipulation by enforcing absolute & relative deviation constraints #### Related contracts * [`OracleSubmitter`](/core-vaults/architecture/oracles/oraclesubmitter): wrapper that forwards reports to the `Oracle`, keeps a report history and exposes the share price as a Chainlink-style feed * [`OracleHelper`](/core-vaults/architecture/oracles/oraclehelper): view helper that computes the prices to report from vault TVL * [`PermissionedChainlinkOracle`](/core-vaults/architecture/oracles/permissionedchainlinkoracle): owner-controlled price feed for assets without a Chainlink feed # OracleHelper Source: https://docs.mellow.finance/core-vaults/architecture/oracles/oraclehelper ### Overview `OracleHelper` is a stateless, view-only contract that computes the prices an oracle reporter should submit for a vault. You give it the vault's total value in the base asset and the price of each asset in base asset terms. It returns the `priceD18` values to pass to `Oracle.submitReports`. The helper accounts for two things that are easy to get wrong offchain: * **Pending redemptions.** Assets already owed to priced async redemptions are excluded from TVL. * **Fee dilution.** Fee shares minted when the report is handled are included, so the reported price already reflects them. ### Function ```solidity theme={null} function getPricesD18(Vault vault, uint256 totalAssets, AssetPrice[] calldata assetPrices) external view returns (uint256[] memory pricesD18) ``` ```solidity theme={null} struct AssetPrice { address asset; uint256 priceD18; // 0 for the base asset; otherwise the value of 1 asset in base asset units, 1e18 precision } ``` **Inputs:** * `vault`: the vault to price. * `totalAssets`: the vault's total value, expressed in the base asset. * `assetPrices`: every asset to report, sorted by address in strictly ascending order. Exactly one entry must have `priceD18 = 0`, and that entry is the base asset. For the other entries, `1e18` means 1 asset equals 1 base asset, and `0.5e18` means 1 asset equals 0.5 base assets. The result has one price per entry, in the same order, ready to submit. ### Calculation 1. **Validate input.** Reverts if the assets are not strictly ascending, or if more than one asset has price 0. 2. **Subtract redeem demand.** For every redeem queue that still has unprocessed requests or unsettled batches (`canBeRemoved()` is `false`), reads `totalDemandAssets` from `getState()`. This is the amount owed to priced but unsettled batches. It converts that demand to the base asset with the given prices and subtracts it from `totalAssets`. Sync and signature redeem queues are skipped because they hold no outstanding demand. Every queue asset must appear in `assetPrices`. 3. **Price the base asset.** Computes: ```solidity theme={null} basePriceD18 = (totalShares + fee) * 1e18 / totalAssets ``` `fee` is `feeManager.calculateFee(vault, baseAsset, minPriceD18, totalShares - feeRecipientShares)`. No performance fee applies at `minPriceD18`, so this is the protocol fee accrued since the last report. 4. **Resolve the performance fee.** If the result is below `minPriceD18`, the share price has reached a new high and a performance fee applies, which in turn depends on the reported price. The helper binary-searches between the result and `minPriceD18` for the smallest price that covers its own fee shares. 5. **Price the other assets.** Each non-base price is `basePriceD18 * assetPrice / 1e18`. 6. **Check the output.** Reverts if any price is 0 or does not fit in `uint224`. If the fee manager has a base asset configured, the zero-priced entry must be that asset, or the call reverts with `OracleHelper: invalid base asset`. ### Errors The helper reverts with string messages: | Message | Reason | | - | - | | `OracleHelper: invalid asset order` | `assetPrices` is not sorted by address in ascending order | | `OracleHelper: multiple base assets` | More than one entry has `priceD18 = 0` | | `OracleHelper: asset not found` | A vault queue asset is missing from `assetPrices` | | `OracleHelper: invalid base asset` | Zero-priced asset differs from the fee manager base asset | | `OracleHelper: invalid price` | A computed price is 0 or exceeds `uint224` | ### Usage notes * The helper only computes prices. It does not submit them, and it holds no roles. * The result is only as accurate as `totalAssets` and the input prices, which come from the offchain valuation. * Call it in the same block, or close to it, as the report submission. Queue demand, total shares and fee state change over time. # OracleSubmitter Source: https://docs.mellow.finance/core-vaults/architecture/oracles/oraclesubmitter ### Overview `OracleSubmitter` is a thin wrapper that sits between offchain reporters and a vault's [`Oracle`](/core-vaults/architecture/oracle). Reports pass through it to the oracle, and it adds two things: * An onchain **history of every report** submitted through it, per asset. * A **Chainlink-style price feed** of the vault share price, denominated in the vault's base asset. The feed lets external protocols, such as lending markets, read the share price with the standard `latestRoundData()` interface instead of integrating the `Oracle` directly. ### Setup ```solidity theme={null} constructor(address admin_, address submitter_, address accepter_, address oracle_) ``` * Reads the vault from `oracle.vault()` and the base asset from `feeManager.baseAsset(vault)`. Reverts with `ZeroBaseAsset` if the vault has no base asset. * Sets `decimals` to the base asset's decimals, or 18 for native ETH. * Grants `DEFAULT_ADMIN_ROLE` to `admin_`. * Grants the oracle's `SUBMIT_REPORTS_ROLE` to `submitter_` and `ACCEPT_REPORT_ROLE` to `accepter_`. These roles are held **in the `OracleSubmitter`**, using the same role IDs as the `Oracle`. For the wrapper to work, the `OracleSubmitter` contract itself must hold `SUBMIT_REPORTS_ROLE` and `ACCEPT_REPORT_ROLE` on the **vault**. The `Oracle` checks roles against the vault, and the wrapper is the account that calls it. Mellow's deployment scripts grant both roles to the submitter contract at vault creation. ### Functions ### `submitReports(IOracle.Report[] reports)` Callable by holders of `SUBMIT_REPORTS_ROLE` in the `OracleSubmitter`. 1. Reverts with `InvalidOrder` unless the first report is for the base asset. 2. Forwards the batch to `oracle.submitReports(reports)`. All oracle checks apply, including timeout, deviation limits and suspicious flags. 3. For each asset, reads back the stored `DetailedReport` and appends it to that asset's history. 4. If the base asset report is not suspicious, updates the share price feed. ### `acceptReports(address[] assets, uint224[] pricesD18, uint32[] timestamps)` Callable by holders of `ACCEPT_REPORT_ROLE` in the `OracleSubmitter`. 1. Calls `oracle.acceptReport(asset, priceD18, timestamp)` for each entry. 2. If an entry is for the base asset, it must be the first entry, or the call reverts with `InvalidOrder`. Accepting the base asset report updates the share price feed. 3. Records the acceptance time in `acceptedAt[asset][index]` for the asset's latest stored report. ### Share price feed The feed stores the inverse of the oracle price: ```solidity theme={null} latestAnswer = 1e36 / priceD18; ``` The oracle's `priceD18` is the number of shares per unit of base asset. `latestAnswer` is therefore the value of one full share (`1e18` share units) in base asset units, using the base asset's `decimals`. | Function | Returns | | - | - | | `latestRoundData()` | `(0, latestAnswer, updatedAt, updatedAt, 0)`. Round IDs are always `0` | | `latestAnswer()` | Latest share price in base asset units | | `getRate()` | `latestAnswer` as `uint256` | | `decimals()` | Decimals of the base asset | | `updatedAt()` | Time the feed was last updated | Suspicious base asset reports do not update the feed until they are accepted. Consumers should check `updatedAt` for staleness, as with any Chainlink feed. ### Report history | Function | Description | | - | - | | `reports(asset)` | Number of reports stored for `asset` | | `reportAt(asset, index)` | The `DetailedReport` (price, timestamp, suspicious flag) at `index` | | `acceptedAt(asset, index)` | Time the report at `index` was accepted, or `0` if it was never accepted | Only reports submitted through this contract are recorded. Reports sent directly to the `Oracle` do not appear in the history. ### Errors | Error | Reason | | - | - | | `ZeroBaseAsset()` | The vault has no base asset configured in the `FeeManager` | | `InvalidOrder()` | The base asset report is not the first entry | ### Deployments `OracleSubmitter` addresses for live vaults are listed on the deployment pages, for example [Lido Earn](/lido-earn/deployments). # PermissionedChainlinkOracle Source: https://docs.mellow.finance/core-vaults/architecture/oracles/permissionedchainlinkoracle ### Overview `PermissionedChainlinkOracle` is a minimal, owner-controlled price feed that implements the `latestRoundData()` part of Chainlink's `AggregatorV3` interface. It provides prices for assets that have no native Chainlink feed. Its main use is as a price source for custom `AaveOracle` deployments. The [`SwapModule`](/core-vaults/architecture/utils/swapmodule) reads those `AaveOracle` deployments to value assets and enforce minimum swap output. This feed is intentionally centralized. Its security depends entirely on the owner, so the owner should be a multisig. ### Constructor parameters | Parameter | Type | Description | | - | - | - | | `owner_` | address | Account allowed to update the price | | `decimals_` | uint8 | Price decimals, at most 36 (for example, 8) | | `initialAnswer_` | int256 | Starting price | | `minAllowedAnswer_` | int256 | Lowest accepted price, inclusive, at least 0 | | `maxAllowedAnswer_` | int256 | Highest accepted price, inclusive | | `description_` | string | Feed description (for example, `"ETH / USD"`) | The constructor reverts with `InvalidDecimals` if `decimals_ > 36`, and with `InvalidAllowedRange` if `minAllowedAnswer_` is negative or greater than `maxAllowedAnswer_`. The price bounds are immutable. ### Functions ### `latestRoundData()` Returns `(0, latestAnswer, latestTimestamp, latestTimestamp, 0)`. `roundId` and `answeredInRound` are always `0`. `getRoundData` and historical rounds are not implemented. ### `updatePrice(int256 newAnswer)` Owner only. Stores a new price and sets `latestTimestamp` to the current block time. Reverts with `InvalidPrice` if `newAnswer` is outside `[minAllowedAnswer, maxAllowedAnswer]`. ### Public state | Variable | Description | | - | - | | `latestAnswer` | Latest price | | `latestTimestamp` | Time of the latest update | | `decimals` | Price decimals | | `description` | Feed description | | `minAllowedAnswer` | Lower price bound | | `maxAllowedAnswer` | Upper price bound | ### Events ```solidity theme={null} event AnswerUpdated(int256 indexed current, uint256 indexed roundId, uint256 updatedAt); ``` `roundId` is always `0`. ### Deployment Mellow deploys instances deterministically with a `PermissionedOracleFactory` using `CREATE2`. The same initialization parameters always produce the same feed address. ### Audit Audited by Nethermind (NM\_0891, April 2026), with one informational note and no other findings. See [Security](/security). # BitmaskVerifier Source: https://docs.mellow.finance/core-vaults/architecture/permissions/bitmaskverifier #### Purpose The `BitmaskVerifier` is a customizable, low-level verifier module that enables **selective call authorization** using **bitmask-based hashing**. It allows a contract to validate whether a function call (defined by `who`, `where`, `value`, and `data`) conforms to a pre-authorized pattern. It supports: * Partial matching of calldata * Exact or wildcard matching on sender, target, or ETH value * Highly gas-efficient verification with minimal storage #### Core Concept: Bitmask-Based Hashing The `BitmaskVerifier` computes a hash over masked components of a transaction and compares it to a stored or expected hash. The verification succeeds if: ```solidity theme={null} calculateHash(bitmask, who, where, value, data) == expectedHash ``` #### Bitmask Format The bitmask is a byte array with the following structure: | Segment | Bytes | Targeted Field | Description | | - | - | - | - | | \[0:32] | 32 bytes | `who` | Mask for the caller address (left-padded to 32 bytes) | | \[32:64] | 32 bytes | `where` | Mask for the target contract address (left-padded to 32 bytes) | | \[64:96] | 32 bytes | `value` | Mask for ETH value (uint256) | | \[96:] | `data.length` | `data` | One byte per calldata byte; used to mask calldata selectively | This structure allows the verifier to: * Fully match addresses and value * Partially match calldata (e.g. permit `approve(x, anyAmount)`) #### Function: `calculateHash` ```solidity theme={null} function calculateHash( bytes calldata bitmask, address who, address where, uint256 value, bytes calldata data ) public pure returns (bytes32) ``` #### Logic This function computes a `keccak256` hash over the masked versions of each input field: 1. `who`, masked by `bitmask[0:32]` 2. `where`, masked by `bitmask[32:64]` 3. `value`, masked by `bitmask[64:96]` 4. Each `data[i]` masked by `bitmask[96+i]` #### Example Use If a bitmask has `0xff` for a given byte, that byte is strictly matched. If `0x00`, the byte is ignored (wildcarded). Mixed values allow partial matching. #### Function: `verifyCall` ```solidity theme={null} function verifyCall( address who, address where, uint256 value, bytes calldata data, bytes calldata verificationData ) public pure returns (bool) ``` #### Input: `verificationData` This input must be ABI-encoded as: ```solidity theme={null} abi.encode(bytes32 expectedHash, bytes bitmask) ``` #### Logic 1. Parses `expectedHash` and `bitmask` from the calldata 2. Verifies that the bitmask length matches `96 + data.length` * 32 for `who`, 32 for `where`, 32 for `value`, and one byte per calldata byte 3. Calls `calculateHash()` and compares it to `expectedHash` Returns: * `true` if the masked call hash matches the expected hash * `false` otherwise #### Use Cases This verifier enables granular control over contract interactions, for example: * **Approvals to a specific contract**: Allow `approve(farmContract, anyAmount)` but block other approvals. * **Partial calldata authorization**: Authorize only the first 4 bytes (function selector) of a call. * **Curated access for specific addresses**: Allow only specific curators to call `delegate(address)` with known targets. * **Value-bound actions**: Authorize only zero-ETH transactions or enforce a cap on `value`. # Consensus Source: https://docs.mellow.finance/core-vaults/architecture/permissions/consensus #### Purpose The `Consensus` contract manages a permissioned set of signers and enforces **multi-signature validation logic** using either EIP-712 or EIP-1271 signatures. It is a lightweight module designed for verifying **offchain consensus** before executing critical actions such as deposit and redemptions via SignatureQueues. It supports: * Threshold-based consensus * Two signature modes: EIP712 (EOA) and EIP1271 (contract-based) * Dynamic signer set management * Stateless, reusable verification interface #### Core Concepts #### Threshold-Based Verification To validate an action, a set of authorized signers must collectively submit signatures. The number of valid signatures must be **greater than or equal to** the configured `threshold`. #### Signature Types Each signer is associated with a `SignatureType`: * `EIP712` – Used for externally owned accounts (standard `ECDSA.recover`) * `EIP1271` – Used for contract accounts (via `isValidSignature()`) #### Storage Layout ```solidity theme={null} struct ConsensusStorage { uint256 threshold; EnumerableMap.AddressToUintMap signers; } ``` * `threshold`: Minimum number of valid signatures required for verification to succeed. * `signers`: Mapping of signer addresses → their configured signature type. #### Initialization ```solidity theme={null} function initialize(bytes calldata data) ``` * Expects `abi.encode(owner)` as input. * Sets the initial owner using `OwnableUpgradeable`. #### Signature Verification #### checkSignatures ```solidity theme={null} function checkSignatures(bytes32 orderHash, Signature[] calldata signatures) public view returns (bool) ``` * Returns `true` if: * At least `threshold` signatures are present * Each signature is: * From an authorized signer * Valid according to the signer's configured signature type * Returns `false` otherwise Signature validation behavior: * `EIP712`: Uses `ECDSA.recover(orderHash, sig)` and matches signer * `EIP1271`: Calls `isValidSignature(orderHash, sig)` on the contract #### requireValidSignatures ```solidity theme={null} function requireValidSignatures(bytes32 orderHash, Signature[] calldata signatures) external view ``` * Same logic as `checkSignatures`, but reverts with `InvalidSignatures` error if validation fails #### Signer Management (Owner-only) #### setThreshold ```solidity theme={null} function setThreshold(uint256 threshold_) external onlyOwner ``` * Sets a new threshold * Must be `> 0` and `≤ signers.length()` * Emits `ThresholdSet` #### addSigner ```solidity theme={null} function addSigner(address signer, uint256 threshold_, SignatureType sigType) external onlyOwner ``` * Adds a new signer with specified signature type * Updates threshold (as part of signer addition) * Reverts if: * `signer == address(0)` * Signer already exists * Emits `SignerAdded` and `ThresholdSet` #### removeSigner ```solidity theme={null} function removeSigner(address signer, uint256 threshold_) external onlyOwner ``` * Removes signer from consensus set * Updates threshold * Reverts if signer not found * Emits `SignerRemoved` and `ThresholdSet` #### View Functions | Function | Returns | | - | - | | `threshold()` | Current consensus threshold | | `signers()` | Total number of signers | | `signerAt(uint256)` | Signer address and type at index | | `isSigner(address)` | Boolean indicating if address is a signer | #### Events * `Initialized(bytes)` * `ThresholdSet(uint256)` * `SignerAdded(address signer, SignatureType)` * `SignerRemoved(address signer)` * `InvalidSignatures(bytes32 hash, Signature[] signatures)` (used in revert) #### Security Considerations * Only the owner (via `OwnableUpgradeable`) may update signer set or threshold * Signatures are stateless and externally verifiable * Replay protection (e.g., nonce checks) must be handled by upstream systems (nonces) * Signers using `EIP1271` are trusted for contract logic – contracts must not be mutable without governance # Permissions Source: https://docs.mellow.finance/core-vaults/architecture/permissions/index In this directory, you will find a detailed per-contract overview of the "Permissions" contract category, including the following modules: \ \ [MellowACL](/core-vaults/architecture/permissions/mellowacl) [Verifier](/core-vaults/architecture/permissions/verifier) [BitmaskVerifier](/core-vaults/architecture/permissions/bitmaskverifier) [Consensus](/core-vaults/architecture/permissions/consensus) [protocols](/core-vaults/architecture/permissions/protocols) # MellowACL Source: https://docs.mellow.finance/core-vaults/architecture/permissions/mellowacl ### Purpose `MellowACL` is a lightweight but extendable access control layer that wraps OpenZeppelin's `AccessControlEnumerableUpgradeable`. It introduces automatic tracking and enumeration of *active roles* to improve governance transparency. This contract is intended to be inherited by modules that require dynamic role management and storage-isolated initialization. ### Responsibilities * Grant and revoke access control roles to addresses * Keep track of all active (i.e., assigned) roles in a dedicated set * Expose enumerable functions for external auditing of granted roles * Emit structured events when roles are added or fully revoked ### Storage Layout ```solidity theme={null} struct MellowACLStorage { EnumerableSet.Bytes32Set supportedRoles; } ``` * `supportedRoles`: A unique set of role identifiers (`bytes32`) currently assigned to any address * Uses a dedicated storage slot derived from: ```solidity theme={null} SlotLibrary.getSlot("MellowACL", name_, version_) ``` ### View Functions ### `supportedRoles() → uint256` Returns the number of currently active roles (i.e., roles with at least one member). ### `supportedRoleAt(index: uint256) → bytes32` Returns the role identifier at the specified index from the active role set. ### `hasSupportedRole(role: bytes32) → bool` Returns `true` if the role is currently active (i.e., assigned to at least one account). ### Internal Logic ### `_grantRole(role: bytes32, account: address) → bool` Grants the specified role to an account. If the role was not previously active, it is added to `supportedRoles`, and `RoleAdded` is emitted. * Inherits from `AccessControlUpgradeable._grantRole` * Emits: ```solidity theme={null} event RoleAdded(bytes32 indexed role) ``` ### `_revokeRole(role: bytes32, account: address) → bool` Revokes the specified role from an account. If the role has no remaining members afterward, it is removed from `supportedRoles`, and `RoleRemoved` is emitted. * Inherits from `AccessControlUpgradeable._revokeRole` * Emits: ```solidity theme={null} event RoleRemoved(bytes32 indexed role) ``` ### Constructor ```solidity theme={null} constructor(string memory name_, uint256 version_) ``` * Computes a deterministic storage slot using `SlotLibrary` * Disables initializer to prevent accidental direct deployment * Should be initialized later via proxy-aware module constructor ### Events * `event RoleAdded(bytes32 indexed role)` * Emitted when a new role is introduced into the system * `event RoleRemoved(bytes32 indexed role)` * Emitted when the last holder of a role is revoked and the role becomes inactive # EigenLayerVerifier Source: https://docs.mellow.finance/core-vaults/architecture/permissions/protocols/eigenlayerverifier ### Overview `EigenLayerVerifier` is a custom `ICustomVerifier` implementation tailored to securely authorize calls to **EigenLayer** contracts like `DelegationManager`, `StrategyManager`, and `RewardsCoordinator`. It uses strict role-based gating, exact calldata matching, and entity-specific validation to ensure that only authorized vaults and bots can interact with EigenLayer staking, delegation, withdrawal, and rewards workflows. ### Purpose This verifier protects EigenLayer operations by: * Ensuring only whitelisted entities (vaults, strategies, operators) can execute actions * Verifying target contracts and function selectors precisely * Enforcing exact calldata encoding to eliminate any ambiguity or abuse ### Role Definitions | Role Constant | Description | | - | - | | `CALLER_ROLE` | Address allowed to initiate EigenLayer calls (typically curators) | | `ASSET_ROLE` | Whitelisted ERC20 token allowed in strategy deposits or withdrawals | | `STRATEGY_ROLE` | Whitelisted EigenLayer strategy contracts | | `OPERATOR_ROLE` | Approved EigenLayer operator address for delegation | | `MELLOW_VAULT_ROLE` | Whitelisted vaults acting as stakers or earners (usually `Subvault` ) | | `RECEIVER_ROLE` | Authorized receivers for claimed rewards | ### Constructor ```solidity theme={null} constructor(address delegationManager_, address strategyManager_, address rewardsCoordinator_, string memory name_, uint256 version_) ``` Initializes the verifier by: * Setting immutable references to EigenLayer's: * `DelegationManager` * `StrategyManager` * `RewardsCoordinator` * Inheriting access control via `OwnedCustomVerifier` ### `verifyCall` ```solidity theme={null} function verifyCall( address who, address where, uint256 value, bytes calldata callData, bytes calldata /* verificationData */ ) external view override returns (bool) ``` ### General Preconditions * `who` must have `CALLER_ROLE` * `value` must be 0 (no ETH allowed) * `callData.length >= 4` (valid selector) ### Validated Targets & Selectors ### 1. **StrategyManager** – `depositIntoStrategy` * `depositIntoStrategy(IStrategy, address asset, uint256 shares)` * Strategy must have `STRATEGY_ROLE` * Asset must have `ASSET_ROLE` * Shares must be non-zero * Calldata must match ### 2. **DelegationManager** * **`delegateTo(address operator, SignatureWithExpiry signature, bytes32 salt)`** * Operator must have `OPERATOR_ROLE` * Calldata must match * **`queueWithdrawals(QueuedWithdrawalParams[] params)`** * Only **one** **`params.length == 1`** allowed * Param must include: * One strategy with `STRATEGY_ROLE` * One deposit share > 0 * Calldata must match * **`completeQueuedWithdrawal(Withdrawal, address[] tokens, bool receiveAsTokens)`** * `receiveAsTokens` must be `true` * Withdrawal must: * Have only one strategy with `STRATEGY_ROLE` * Have `staker` with `MELLOW_VAULT_ROLE` * `tokens.length == 1` and token must have `ASSET_ROLE` * Calldata must match ### 3. **RewardsCoordinator** – `processClaim` * **Selector:** `processClaim(RewardsMerkleClaim claimData, address receiver)` * **Checks:** * `claimData.earnerLeaf.earner` must have `MELLOW_VAULT_ROLE` * `receiver` must have `RECEIVER_ROLE` * Calldata must match ### Security Properties * **Role enforcement:** Prevents unauthorized usage of EigenLayer functions * **Exact calldata match:** Avoids incorrect encoding or maliciously crafted data * **Zero ETH transfers:** Disallows unexpected native token usage * **Single param enforcement (withdrawals):** Minimizes complexity and risk surface # ERC20Verifier Source: https://docs.mellow.finance/core-vaults/architecture/permissions/protocols/erc20verifier ### Overview `ERC20Verifier` is a role-driven `ICustomVerifier` implementation that enforces strict, granular permissioning over ERC20 `approve` and `transfer` function calls. It builds upon `OwnedCustomVerifier`, using `MellowACL`-style roles to validate the **caller**, **target asset**, and **recipient** of each operation. This verifier is designed for use in **modular vaults** such as `SubVault` where only specific ERC20 operations should be allowed through a customizable permission matrix. ### Purpose To allow or deny ERC20 `approve` and `transfer` calls based on: * The **caller** (must have `CALLER_ROLE`) * The **asset address** (must have `ASSET_ROLE`) * The **recipient** (must have `RECIPIENT_ROLE`) * Additionally: * `transfer` must not be for zero amount * `approve` allows any amount * `value` sent with the call must be `0` * Only exact calldata is accepted (no encoding variation or garbage data) ### Roles Each permission check is mapped to a distinct `bytes32` role: | Role Constant | Purpose | | - | - | | `ASSET_ROLE` | Marks which ERC20 tokens are allowed to be interacted with | | `CALLER_ROLE` | Who is allowed to perform `approve` or `transfer` | | `RECIPIENT_ROLE` | Who is allowed to receive tokens (for `transfer`) or get approval (for `approve`) | These roles are expected to be configured via the `initialize()` function inherited from `OwnedCustomVerifier`. ### Contract Behavior ### Constructor ```solidity theme={null} constructor(string memory name_, uint256 version_) ``` * Passes initialization parameters to `OwnedCustomVerifier` and disables further initializers ### `verifyCall` Function ```solidity theme={null} function verifyCall( address who, address where, uint256 value, bytes calldata callData, bytes calldata /* verificationData */ ) external view override returns (bool) ``` ### Summary: Checks if a specific ERC20 call is authorized. ### Logic Steps: 1. **Pre-checks**: * Must be a zero-ETH call: `value == 0` * Calldata must be exactly 68 bytes: 4-byte selector + 32 bytes address + 32 bytes uint * `where` (the token address) must have `ASSET_ROLE` * `who` (the caller, usually curator) must have `CALLER_ROLE` 2. **Selector Validation**: * Accepts only two ERC20 functions: * `approve(address,uint256)` * `transfer(address,uint256)` 3. **Recipient & Amount Validation**: * `to` address must have `RECIPIENT_ROLE` * For `transfer`: * `amount` must not be zero * `to` must not be zero address in any case 4. **Exact Calldata Matching**: * Ensures call is not forged via alternate encodings: ```solidity theme={null} keccak256(abi.encodeWithSelector(selector, to, amount)) == keccak256(callData) ``` ### Returns: * `true` if all checks pass * `false` otherwise ### Security Considerations * Prevents misuse of `approve` and `transfer` by enforcing: * Strict role-based gating * Zero ETH payload enforcement * Calldata normalization to eliminate encoding ambiguity * Ensures no contract or address receives funds or allowances without being explicitly whitelisted # Protocols Source: https://docs.mellow.finance/core-vaults/architecture/permissions/protocols/index In this directory, you will find a detailed overview of the specific verifiers, including the following: \ \ [OwnedCustomVerifier](/core-vaults/architecture/permissions/protocols/ownedcustomverifier) [ERC20Verifier](/core-vaults/architecture/permissions/protocols/erc20verifier) [SymbioticVerifier](/core-vaults/architecture/permissions/protocols/symbioticverifier) [EigenLayerVerifier](/core-vaults/architecture/permissions/protocols/eigenlayerverifier) # OwnedCustomVerifier Source: https://docs.mellow.finance/core-vaults/architecture/permissions/protocols/ownedcustomverifier ### Overview `OwnedCustomVerifier` is an **abstract base contract** for implementing `ICustomVerifier`-compatible verifiers with configurable role-based access control. It integrates with `MellowACL` and provides a flexible initialization mechanism for dynamic permission setup. This verifier is designed to be used in **`Verifier.sol`** as a custom verifier, \*\*\*\*where specific calls must pass access control checks based on predefined roles. ### Key Components ### Inherits: * `ICustomVerifier`: Interface used by the `Verifier` contract for permission checks * `MellowACL`: Upgradeable, role-based access control module compatible with OpenZeppelin's `AccessControl` ### Constructor ```solidity theme={null} constructor(string memory name_, uint256 version_) MellowACL(name_, version_) ``` * Initializes the underlying `MellowACL` module with `name_` and `version_` * Disables further initialization to prevent misuse in logic contracts (`_disableInitializers()`) ### Initialization ```solidity theme={null} function initialize(bytes calldata data) external initializer ``` * Initializes access control roles * Decodes input as: ```solidity theme={null} (address admin, address[] memory holders, bytes32[] memory roles) ``` * Logic: * Sets `admin` as the contract's `DEFAULT_ADMIN_ROLE` * Grants each `roles[i]` to `holders[i]` * Reverts with `ZeroValue` if: * `admin == address(0)` * Any holder is zero address * Any role is `DEFAULT_ADMIN_ROLE` ### Usage Pattern This base contract does **not** implement the `verifyCall()` method itself. Instead, it is expected to be **inherited and extended** by a concrete verifier contract that implements the permission logic based on role membership (e.g., checking `hasRole(role, who)`). This allows teams to quickly implement custom verifiers that enforce arbitrary permissions (e.g., allow certain addresses to `approve`, `transfer`, or `delegate`) based on **assigned roles** instead of hardcoded logic. # SymbioticVerifier Source: https://docs.mellow.finance/core-vaults/architecture/permissions/protocols/symbioticverifier ### Overview `SymbioticVerifier` is a custom `ICustomVerifier` implementation used to authorize interactions with the Symbiotic protocol. It restricts access to `deposit`, `withdraw`, `claim`, and `claimRewards` calls across Symbiotic vaults and farm contracts. All permissions are tightly scoped using role-based access control via `MellowACL`. This verifier ensures that only allowed addresses (typically curators) can perform specific actions within the Symbiotic ecosystem. ### Purpose The verifier ensures that: * Only whitelisted vaults can act on behalf of themselves in Symbiotic vaults and farms * All interactions are strictly validated against exact calldata to prevent misuse or encoding variation * Only allowed selectors and targets can be used ### Role Definitions | Role Constant | Description | | - | - | | `CALLER_ROLE` | Who is allowed to initiate Symbiotic operations (typically curators) | | `MELLOW_VAULT_ROLE` | Addresses that are allowed to be the recipient of deposits, withdrawals, or claims (usually Subvaults) | | `SYMBIOTIC_VAULT_ROLE` | Contracts that are approved as Symbiotic vault | | `SYMBIOTIC_FARM_ROLE` | Contracts that are approved as Symbiotic farm | ### Constructor ```solidity theme={null} constructor(address vaultFactory_, address farmFactory_, string memory name_, uint256 version_) ``` ### `verifyCall` ```solidity theme={null} function verifyCall( address who, address where, uint256 value, bytes calldata callData, bytes calldata /* verificationData */ ) public view returns (bool) ``` ### High-Level Behavior * Verifies caller (`who`) has `CALLER_ROLE` * Matches target contract (`where`) with either a Symbiotic vault or farm * Validates exact function selector and arguments using full `keccak256(callData)` hash * Rejects any calls with non-zero ETH value ### Supported Calls | Target Type | Function | Signature | Additional Checks | | - | - | - | - | | Symbiotic Vault | `deposit(onBehalfOf, amount)` | `ISymbioticVault.deposit.selector` | `onBehalfOf` must have `MELLOW_VAULT_ROLE`, `amount > 0` | | Symbiotic Vault | `withdraw(claimer, amount)` | `ISymbioticVault.withdraw.selector` | `claimer` must have `MELLOW_VAULT_ROLE`, `amount > 0` | | Symbiotic Vault | `claim(recipient, epoch)` | `ISymbioticVault.claim.selector` | `recipient` must have `MELLOW_VAULT_ROLE` | | Symbiotic Farm | `claimRewards(recipient, token, data)` | `ISymbioticStakerRewards.claimRewards.selector` | `recipient` must have `MELLOW_VAULT_ROLE`, `token != 0x0` | * For all calls, the calldata must exactly match the selector and parameters * All other selectors or targets are denied ### Security Properties * **Strict call gating**: Only explicitly allowed selectors, targets, and roles pass * **Calldata hash check**: Enforces strict encoding to avoid alternate ABI variants or garbage data * **Zero-value enforcement**: Prevents accidental ETH transfers * **Factory pattern compatibility**: Target contracts can be validated indirectly via registries # Verifier Source: https://docs.mellow.finance/core-vaults/architecture/permissions/verifier ### Purpose The `Verifier` contract is a multi-mode permissioning module for verifying and enforcing call-level access control across vault-connected modules. It supports: * Onchain allowlists using hashed shortened calls (`CompactCall`) * Merkle tree-based validation for compact merkle, extended merkle and custom verifier verification types * Delegated verification logic through external custom verifiers (`ICustomVerifier`) This contract enables secure and modular delegation of operational permissions ### Core Responsibilities * Validates function calls from external actors (e.g., operators, curators) or strategy contracts (strategies) * Grants or revokes execution rights using onchain and off-chain mechanisms * Ensures that only whitelisted or merkle-authenticated calls are allowed * Integrates with vault-based role system via `IAccessControl` ### Roles and Access * `SET_MERKLE_ROOT_ROLE`: Role allowed to update the active Merkle root * `CALLER_ROLE`: Role required by initiators of authorized calls * `ALLOW_CALL_ROLE`: Grants ability to add compact calls to allowlist * `DISALLOW_CALL_ROLE`: Grants ability to remove compact calls from allowlist ### Storage Layout ```solidity theme={null} struct VerifierStorage { address vault; bytes32 merkleRoot; EnumerableSet.Bytes32Set compactCallHashes; mapping(bytes32 => CompactCall) compactCalls; } ``` * `vault`: Vault contract that owns the verifier (must support `IAccessControl`) * `merkleRoot`: Merkle root for off-chain verified call proofs * `compactCallHashes`: Set of hashes representing allowed compact calls * `compactCalls`: Optional mapping for reverse lookup of call metadata by hash ### Verification Types ```solidity theme={null} enum VerificationType { ONCHAIN_COMPACT, MERKLE_COMPACT, MERKLE_EXTENDED, CUSTOM_VERIFIER } ``` * **ONCHAIN\_COMPACT**: Checks `CompactCall` (who | where | selector) hash against internal set * **MERKLE\_COMPACT**: Verifies Merkle proof of `CompactCall` (who | where | selector) hash * **MERKLE\_EXTENDED**: Verifies Merkle proof of `ExtendedCall` (who | where | value | callData) hash * **CUSTOM\_VERIFIER:** Delegates full verification to an external verifier ### Call Structures ```solidity theme={null} struct CompactCall { address who; address where; bytes4 selector; } struct ExtendedCall { address who; address where; uint256 value; bytes data; } struct VerificationPayload { VerificationType verificationType; bytes verificationData; bytes32[] proof; } ``` * `CompactCall`: Encodes permissioned call using address and selector * `ExtendedCall`: Encodes full call (selector + calldata + ETH value) * `VerificationPayload`: Contains verification metadata and proof ### View Functions * `vault()`: Returns the associated vault contract * `merkleRoot()`: Returns current Merkle root * `allowedCalls()`: Returns number of compact calls in allowlist * `allowedCallAt(index)`: Returns `CompactCall` at index from internal set * `isAllowedCall(who, where, callData)`: Checks if `CompactCall` is explicitly allowed * `hashCall(CompactCall)`: Returns keccak256 hash of compact call * `hashCall(ExtendedCall)`: Returns keccak256 hash of extended call ### Verification Functions ### `verifyCall(...)` ```solidity theme={null} function verifyCall( address who, address where, uint256 value, bytes calldata data, VerificationPayload calldata payload ) external view; ``` * Validates call permissions using the chosen `VerificationType` * Reverts with `VerificationFailed` on failure ### `getVerificationResult(...) → bool` ```solidity theme={null} function getVerificationResult( address who, address where, uint256 value, bytes calldata data, VerificationPayload calldata payload ) external view returns (bool); ``` * Returns `true` if the verification succeeds, `false` otherwise Verification decision logic: * `ONCHAIN_COMPACT`: Validate hash against stored allowlist * `MERKLE_COMPACT`: Validate Merkle proof of compact hash * `MERKLE_EXTENDED`: Validate Merkle proof of full hash * `CUSTOM_VERIFIER`: Validate Merkle proot of the verification payload and delegate validation to external contract ### Mutable Functions * `initialize(bytes calldata initParams)`: * Accepts `abi.encode(address vault_, bytes32 merkleRoot_)` * Sets the vault address and initial Merkle root * `setMerkleRoot(bytes32 root)`: * Updates Merkle root (requires `SET_MERKLE_ROOT_ROLE`) * `allowCalls(CompactCall[] calldata calls)`: * Adds compact calls to allowlist * Reverts on duplicates (calls already allowed) * `disallowCalls(CompactCall[] calldata calls)`: * Removes calls from allowlist * Reverts if call is not found in allowlist ### Initialization ```solidity theme={null} function initialize(bytes calldata initParams) external initializer; ``` * `initParams` format: `abi.encode(address vault_, bytes32 merkleRoot_)` # DepositQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/depositqueue ### Overview The `DepositQueue` contract enables asynchronous asset deposits into vaults using a time-delayed, oracle-priced queuing mechanism. Deposits are not processed immediately. Instead, users submit requests that are later fulfilled when an external price oracle submits a valid report. This enables batching (based on Fenwick Tree data structure), mitigates front-running, and facilitates accurate share pricing. ### Deposit Lifecycle ### Step 1: **User Deposits** * A user submits a deposit request via `deposit(assets, referral, merkleProof)`. * The deposited amount is stored as a `(timestamp, value)` checkpoint under `requestOf[msg.sender]`. * Each account can have **only one pending request** at a time. * Deposits are validated via optional Merkle whitelist logic (using `merkleProof`) or onchain mapping (if `flags.hasWhitelist()` returns true). * If a previous request exists and is already claimable, `deposit` claims it automatically first. If it is still pending, the call reverts with `PendingRequestExists`, and the user must wait for the oracle report or cancel the request. * The deposited amount is added to the vault's pending assets in the `RiskManager`. The call reverts with `LimitExceeded` if this would exceed the vault limit. ### Step 2: **Oracle Report** * Oracle submits a report via the `handleReport(priceD18, timestamp)` method. * The queue validates the report: * It must be called by the vault. * The timestamp must be in the past. * Price must be non-zero. * The queue handles deposit requests that are pending for longer than `depositInterval` seconds (the interval is specified in the oracle's security params). * The contract applies the deposit fee to the reported price and stores the reduced price in `prices`. It uses that price to convert accumulated assets to shares. * Converted shares are allocated but not minted yet. Deposit fee shares are minted to the fee recipient immediately. * The deposited assets move from the queue to the vault, the `RiskManager` moves them from pending assets to the vault balance, and the vault runs the queue's deposit hook. * Events: `ReportHandled` is emitted. ### Step 3: **User Claims** * A user calls `claim(account)` to mint and receive previously allocated shares. * The number of shares is computed as: ```solidity theme={null} uint256 shares = (request.assets * reducedByDepositFeePriceD18) / 1e18; ``` * Shares are minted to the user via `mintAllocatedShares`. ### Cancellation * A user may cancel a pending request using `cancelDepositRequest()`. * Cancellation reverts if the request has already become claimable (i.e., processed by an oracle report). * Refund is issued in the original asset amount. * Event: `DepositRequestCanceled` is emitted. ### Query Methods * `claimableOf(address account)`: Returns how many shares are currently claimable for a given user. * `requestOf(address account)`: Returns the `(timestamp, amount)` tuple of a user's current pending request. * `unclaimedRequests()`: Returns the number of requests that have not been claimed or canceled. The queue can be removed (`canBeRemoved()`) only when this is zero. ### Internal Mechanics The system tracks all deposit requests and prices using the following structures: * `Checkpoints.Trace224 prices`: Stores historical oracle-reported prices keyed by timestamp. * `mapping(address => Checkpoints.Checkpoint224) requestOf`: Maps users to their active deposit requests. * `FenwickTreeLibrary.Tree requests`: A prefix-sum data structure tracking asset totals across compressed timestamps. * `uint256 handledIndices`: Tracks the last fully processed request index, ensuring each oracle report progresses the queue. ## Scalability Challenge Vaults may face **thousands of deposit requests daily**. Processing each one individually leads to significant gas costs or even OOG. To optimize: > A Fenwick Tree (Binary Indexed Tree) is used to efficiently manage aggregate deposit data by timestamp. ### Fenwick Tree Mechanics * **On Deposit**: When a user deposits an amount `A` at time `T`, the system performs: `fenwickTree[T] += A` * **On Cancellation**: If the user cancels the request: `fenwickTree[T] -= A` * **On Oracle Report**: During report at `reportTimestamp`, the system calculates: ```solidity theme={null} fenwickTree.getSum(latestHandledTimestamp + 1, reportTimestamp - depositInterval) ``` This determines the **total amount** eligible for conversion into vault shares at the reported price. ### Lazy Propagation of Shares Rather than eagerly updating each user balance during `handleReport`, the vault employs **lazy propagation**: * Each user's **claimable shares** are finalized only during subsequent calling `claim()`. * This significantly reduces processing cost during batch report execution. ### Timestamp Compression To minimize storage writes and reads used by `FenwickTree.sol` the system uses coordinate compression, storing only timestamps where actual deposit requests occurred. This compression strategy ensures that the Fenwick Tree remains compact, even with high-frequency usage. ### Key **Invariants** 1. **Single Active Request** Each user may have at most one deposit request. A new deposit claims a claimable request automatically, but reverts while the previous request is still pending. 2. **Delayed Execution** Deposit processing requires an oracle report submitted after a configured `depositInterval`. 3. **Lazy Claiming** Deposits are converted to shares during oracle processing, but users must call `claim()` to receive them. 4. **Whitelist Enforcement** Deposits may require Merkle proof for depositor whitelisting. ### Events * `DepositRequested(address account, address referral, uint224 assets, uint32 timestamp)`: Emitted on new deposit submission. * `DepositRequestCanceled(address account, uint256 assets, uint32 timestamp)`: Emitted when a request is canceled and assets refunded. * `DepositRequestClaimed(address account, uint256 shares, uint32 timestamp)`: Emitted when deposit shares are successfully claimed. * `ReportHandled(uint224 priceD18, uint32 timestamp)`: Emitted when an oracle report is processed. ### Errors * `DepositNotAllowed()`: Depositor not whitelisted. * `PendingRequestExists()`: Existing request not yet processed or claimed. * `ClaimableRequestExists()`: Attempting to cancel after request has become claimable. * `NoPendingRequest()`: No existing request to cancel. * `ZeroValue()`: Input value is zero. * `InvalidReport()`: Oracle report failed validation. * `Forbidden()`: Unauthorized caller. * `QueuePaused()`: Deposits disabled via vault pause mechanism. # Queues Source: https://docs.mellow.finance/core-vaults/architecture/queues/index In this directory, you will find a detailed per-contract overview of the "Queues" contract category, including the following queues: [Queue](/core-vaults/architecture/queues/queue) [DepositQueue](/core-vaults/architecture/queues/depositqueue) [RedeemQueue](/core-vaults/architecture/queues/redeemqueue) [SignatureQueue](/core-vaults/architecture/queues/signaturequeue) [SignatureDepositQueue](/core-vaults/architecture/queues/signaturedepositqueue) [SignatureRedeemQueue](/core-vaults/architecture/queues/signatureredeemqueue) [SyncQueue](/core-vaults/architecture/queues/syncqueue) [SyncDepositQueue](/core-vaults/architecture/queues/syncdepositqueue) [SyncRedeemQueue](/core-vaults/architecture/queues/syncredeemqueue) # Queue Source: https://docs.mellow.finance/core-vaults/architecture/queues/queue ### Overview The `Queue` contract provides a shared foundation for **time-gated asset processing** in systems like `DepositQueue` and `RedeemQueue`. It tracks user requests via timestamped checkpoints and processes them using oracle-based pricing. This abstract module is **not directly deployable** but is designed to be extended by concrete implementations, which define the behavior of `_handleReport` and allowed user actions (deposit / redeem functions). ### Purpose * Serves as a **modular base** for deposit/redeem queues * Enforces **Vault request processing initially triggered by Oracle** * Stores **timestamped user action traces** via `Checkpoints.Trace224` * Ensures **vault-controlled access** and proper **report validation** ### Derived contracts * `DepositQueue`: handles queued deposits after a delay * `RedeemQueue`: handles redemption requests similarly ### Use Cases * Prevents manipulation by requiring **delayed processing** relative to price updates ### Storage Structure ```solidity theme={null} struct QueueStorage { address asset; // Token/ETH managed by this queue address vault; // Vault that owns this queue Checkpoints.Trace224 timestamps; // Timeline of requests } ``` * **asset**: token used for this queue (ERC20 or native ETH) * **vault**: only this address can call `handleReport(...)` * **timestamps**: request history used in the implementations ### Initialization ```solidity theme={null} function __Queue_init(address asset_, address vault_) internal ``` * Must be called by child contracts * Initializes asset, vault, and creates a starting checkpoint ### Oracle Integration ```solidity theme={null} function handleReport(uint224 priceD18, uint32 timestamp) external ``` * Called by the vault when an oracle report is available * Verifies: * Caller is the `vault` * Price is non-zero * Timestamp is in the past (timestamp \< block.timestamp) * Internally delegates to `_handleReport(...)` (must be implemented by child) ### Abstract Hook ```solidity theme={null} function _handleReport(uint224 priceD18, uint32 timestamp) internal virtual ``` Must be implemented by child classes to: * Read and process requests from `_timestamps()` * Apply pricing logic to convert shares↔assets * Mint/burn shares, transfer tokens, etc. ### View Functions | Function | Description | | - | - | | `vault()` | Returns the controlling vault address | | `asset()` | Returns the ERC20/native token used by the queue | | `canBeRemoved()` | Not implemented in `Queue` (optional for children) | ### Internal Helpers | Function | Description | | - | - | | `_timestamps()` | Returns the internal `Checkpoints.Trace224` structure | | `_queueStorage()` | Loads queue storage using custom storage slot | | `_queueStorageSlot` | Computed using SlotLibrary to prevent conflicts | ### Events ```solidity theme={null} event ReportHandled(uint224 priceD18, uint32 timestamp) ``` * Emitted when `handleReport()` completes * Signals that all eligible requests up to `timestamp` were processed ### Errors | Error | Reason | | - | - | | `ZeroValue()` | Called with `0` address or value | | `Forbidden()` | Caller not authorized (e.g., not the vault) | | `InvalidReport()` | Oracle report is zero-priced or timestamp is invalid | | `QueuePaused()` | Reserved for future ACL/pause integration | # RedeemQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/redeemqueue ### Purpose The `RedeemQueue` contract enables delayed, batched redemptions of vault shares into underlying assets. Redemptions are processed in two phases: 1. **Oracle pricing** – Shares are priced via a trusted price report. 2. **Liquidity settlement** – Vault liquidity is allocated to fulfill priced requests. This separation supports asynchronous liquidity management, gas efficiency, and protection against griefing. ### Overview The `RedeemQueue` enables users to convert their vault shares into underlying assets, introducing a **time delay** enforced by an oracle-defined `redeemInterval`. It maintains the following core invariants: 1. **Request Format**: Each request is structured as a `(shares, timestamp)` pair. 2. **Non-Cancellable**: Redemption requests **cannot** be cancelled to prevent griefing (e.g., submitting and canceling after unstaking starts). 3. **Multiple Requests Allowed**: Users may submit multiple independent redemption requests. Upon receiving an oracle report at `reportTimestamp`, the system processes all requests with: ```solidity theme={null} timestamp <= reportTimestamp - redeemInterval ``` On the next step these vault shares are converted to assets at the price specified in the oracle report in this step. ### Liquidity Processing (Two-Stage) To enable flexible and asynchronous liquidity management, redemption is handled in **two distinct phases**: 1. **Post-Request**: Vault curators monitor and, if needed, pull liquidity from external protocols. 2. **Post-Oracle Report**: * Once a valid report is submitted and sufficient liquidity is available, * Anyone can call `handleBatches(n)` on the `RedeemQueue`. The function has no access control, and in practice the curator or a keeper calls it, * This action triggers the movement of required assets from the vault (and associated subvaults) to process redemption requests. ### Function: `redeem` ```solidity theme={null} function redeem(uint256 shares) external nonReentrant ``` 1. Reverts with `ZeroValue` if `shares` is zero, or with `QueuePaused` if the vault has paused the queue. 2. Locks `shares` from the caller with `shareManager.lock(caller, shares)`. Locked shares move to the share manager's own balance and no longer count as the caller's active shares. 3. Unless the caller is the fee recipient, computes the redeem fee with `feeManager.calculateRedeemFee(shares)`. The fee is burned from the locked shares, minted to the fee recipient and subtracted from the request. 4. Adds the remaining shares to the request at the current timestamp and emits `RedeemRequested(account, shares, timestamp)`. Requests made in the same block are merged into one entry for that timestamp. ### Function: `handleBatches` ```solidity theme={null} function handleBatches(uint256 batches) external nonReentrant returns (uint256 counter) ``` Settles up to `batches` priced batches, in order, starting from `batchIterator`: 1. Reads available liquidity with `vault.getLiquidAssets()`, which uses the queue's redeem hook. 2. Takes batches while their cumulative `assets` fit within that liquidity, and stops at the first batch that does not fit. 3. Calls `vault.callHook(demand)`, so the redeem hook pulls the assets from subvaults and the vault transfers them to the queue. 4. Decreases the vault balance in the `RiskManager` and `totalDemandAssets`, advances `batchIterator` and emits `RedeemRequestsHandled(counter, demand)`. Locked shares are burned when an oracle report prices them, not in `handleBatches`. ### Scalability Approach Unlike deposits, **redemption requests are never cancelled**, which allows for a simplified and gas-efficient tracking model: A prefix sum array is used to efficiently manage cumulative share redemptions over time. ### Redemption Processing Logic * **On Redemption**: When a user redeems `amount` of shares at time `T`, the system logs: `prefixSum[T] += amount` * **On Oracle Report**: At `reportTimestamp`, all requests with: ```solidity theme={null} timestamp <= reportTimestamp - redeemInterval ``` are priced into a new batch. The batch's shares are burned from the share manager's locked balance, and its asset value (`shares * 1e18 / priceD18`) is added to `totalDemandAssets`. * **Post-Processing**: * The curator ensures the necessary asset liquidity is available, * Then anyone, usually the curator or a keeper, calls `handleBatches()` to finalize processing. * **User Claim**: After requests are processed, users can call: ```solidity theme={null} claim(receiver, timestamps[]) ``` to claim assets for their requested vault shares corresponding to each processed timestamp. ### Storage Layout All internal state is maintained in `RedeemQueueStorage`, including: | Field | Description | | - | - | | `handledIndices` | Tracks number of oracle checkpoints that have been priced | | `batchIterator` | Index of the next unfulfilled batch | | `totalDemandAssets` | Total asset amount needed to fulfill pending batches | | `totalPendingShares` | Total shares in requests that are not yet claimable | | `requestsOf` | Maps `address → (timestamp → shares)` for pending user requests | | `prefixSum` | Maps `timestamp index → shares` for batch creation and summation | | `batches` | Array of `Batch` structs; each batch tracks fulfilled assets and shares | | `prices` | Oracle-reported price checkpoints, indexed by timestamp | ### Structs ### `Request` Represents a single redemption request from a user: * `timestamp`: When the request was submitted. * `shares`: Amount of vault shares being redeemed. * `isClaimable`: Set to true after batch is fulfilled. * `assets`: Amount of assets claimable for this request (set after pricing). ### `Batch` Represents a priced redemption batch: * `assets`: Total value fulfilled for the batch (via oracle `shares / report.price`). * `shares`: Total shares matched in this batch. ### View Functions ### `requestsOf(account, offset, limit)` Returns paginated redemption request data for the specified account. Each request includes: * Timestamp * Shares * Claimable status * Asset amount ### `batchAt(index)` Returns the `(assets, shares)` for a given redemption batch. ### `getState()` Returns core system state: * Current `batchIterator` (next unfulfilled batch index) * Total `batches` * Total `demandedAssets` still awaiting liquidity * Total `pendingShares` that are not yet claimable ### State Transition Guarantees 1. **Non-Cancellable Requests** * Prevents griefing where a user requests redemption, causing curator to pull liquidity, then cancels. 2. **Price Separation** * Oracle reports must be delayed by at least`redeemInterval` seconds from the original request. 3. **Asynchronous Fulfillment** * Liquidity can be managed independently of oracle report submission. ### Events * `RedeemRequested(account, shares, timestamp)` * `RedeemRequestClaimed(account, receiver, assets, timestamp)` * `RedeemRequestsHandled(counter, demand)` # SignatureDepositQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/signaturedepositqueue ### Purpose `SignatureDepositQueue` extends `SignatureQueue` to enable **instant deposit** of assets into a vault, bypassing the standard onchain `DepositQueue` mechanism. It leverages **off-chain approvals** signed by a trusted consensus group, using **EIP-712** or **EIP-1271**-compliant signatures, to authorize asset inflows and minting of vault shares. This contract is optimized for high-trust environments requiring immediate asset onboarding while maintaining onchain price safety guarantees. ### Key Features * **Instant deposit execution** with no queuing delay * **EIP-712 signed orders** with nonce-based replay protection * **Vault share minting** at off-chain pre-agreed price * **Fully integrated with vault accounting and share manager** * **No deposit fee** applied (unlike possible fees in `DepositQueue`) ### Workflow 1. A consensus group signs an `Order` authorizing a user deposit: * Includes asset amount (`ordered`) and shares to mint (`requested`) * Binds the request to a specific queue and vault * Includes a nonce and expiration timestamp 2. User submits the order onchain by calling `deposit` function: * The contract validates the order using signatures and price logic * Receives tokens from the user * Transfers these tokens to the vault * Mints shares to the specified recipient * Updates vault internal balance and executes post-deposit hook ### Function: `deposit` ```solidity theme={null} function deposit(Order calldata order, IConsensus.Signature[] calldata signatures) external payable nonReentrant ``` ### Parameters: * `order`: A signed `Order` struct including deposit parameters * `signatures`: Signatures from the off-chain consensus validating the order ### Steps: 1. `validateOrder(...)`: * Confirms order is not expired * Confirms order is intended for this queue * Confirms correct asset, caller, and nonce * Validates off-chain signatures * Computes and validates asset/share price using vault oracle 2. Increments the caller's nonce to prevent replay 3. Transfers `order.ordered` assets from the caller to this contract 4. Transfers these assets into the vault 5. Calls `vault.callHook(...)` for any optional strategy logic 6. Notifies the vault's `RiskManager` of the new deposit 7. Mints `order.requested` shares to `order.recipient` 8. Emits `OrderExecuted` event ### Security Considerations * **Consensus signatures** are required to prevent unauthorized deposits * **Oracle validation** ensures price sanity even in trusted setups * **Replay protection** enforced using per-user nonces * No deposit can proceed if: * Asset or queue mismatch * Caller mismatch * Nonce is reused * Off-chain price is out of oracle bounds # SignatureQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/signaturequeue ### Purpose The `SignatureQueue` enables **instant** user deposits or redemptions using **off-chain signed approvals** from a trusted consensus group. This queue type bypasses the normal time-delayed queuing mechanism (e.g., `DepositQueue`, `RedeemQueue`) by verifying orders via **EIP-712** or **EIP-1271** signatures, offering fast-lane access for users while preserving oracle-bound price safety. ### Key Features * **Instant execution** without waiting for oracle price reports * **Nonce-based signature protection** to prevent replay attacks * **EIP-712/EIP-1271 compatible** signed orders * **Oracle price validation** enforced onchain * **Stateless and removable** (i.e., does not accumulate shares or process claims) * **Fee-bypassed**: No `depositFee` or `redeemFee` is charged for actions via this queue ### High-Level Workflow 1. Off-chain consensus actors (e.g., operators, curators, admins) generate signed `Order` messages. 2. A user submits this order to the `SignatureQueue` contract for execution. 3. The queue: * Verifies the order signature * Validates nonce, queue address, asset match, deadline, and caller * Computes the implied asset/share price and checks it against the vault's oracle 4. If all checks pass, the order is executed atomically. ### Order Structure Each order encapsulates all the necessary metadata for verification: ```solidity theme={null} struct Order { uint256 orderId; // Off-chain tracking ID address queue; // Must match queue address address asset; // Token involved in deposit/redeem address caller; // Must match msg.sender address recipient; // Recipient of assets or shares uint256 ordered; // Assets in (deposit) or shares out (redeem) uint256 requested; // Shares out (deposit) or assets in (redeem) uint256 deadline; // Expiration timestamp uint256 nonce; // Unique per caller } ``` ### Signature Validation * Orders are signed by a quorum of validators registered in the `Consensus` contract. * Signatures can conform to: * **EIP-712** (structured message hashing) * **EIP-1271** (smart contract-based signature schemes) The order hash is computed via: ```solidity theme={null} keccak256( abi.encode( ORDER_TYPEHASH, order.orderId, order.queue, order.asset, order.caller, order.recipient, order.ordered, order.requested, order.deadline, order.nonce ) ); ``` ### Price Safety Check After signature verification, `SignatureQueue` uses the vault's `Oracle` to validate the price: * **Deposits**: `price = requestedShares / depositedAssets` * **Redemptions**: `price = burnedShares / redeemedAssets` * Oracle must confirm: * Price is within allowed bounds * Price is not marked as suspicious Otherwise, the operation is rejected with `InvalidPrice`. ### Storage Layout ```solidity theme={null} struct SignatureQueueStorage { address consensus; // Signature validator contract address vault; // Parent vault (Vault.sol) address asset; // Supported ERC20 token or native ETH mapping(address => uint256) nonces; // Per-user nonces } ``` ### Interface Compatibility Despite not using claimable balances, `SignatureQueue` implements stub methods for compatibility with the `IQueue` interface: * `claimableOf(...) → 0` * `claim(...) → false` * `handleReport(...)`: no-op * `canBeRemoved() → true`: confirms it has no persistent state ### Events ```solidity theme={null} event OrderExecuted(Order order, IConsensus.Signature[] signatures); ``` Emitted after a signed order is successfully executed. ### Security Assumptions * Only **trusted off-chain actors** (consensus group) are authorized to sign orders. * Price quotes must match oracle-defined asset/share rates. * Users cannot reuse old signatures due to nonce tracking. * Orders must be executed before `deadline`. ### Use Cases * **Instant UX**: bypassing delays in `DepositQueue` or `RedeemQueue` * **Institutional integrations**: where trusted relayers or coordinators pre-sign valid actions * **Fallback mechanism**: during oracle lags or downtime ### Limitations * Does not charge fees (unlike time-delayed queues) * Requires trusted off-chain actors * Less decentralized if consensus actors are not well-audited or rotated # SignatureRedeemQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/signatureredeemqueue ### Purpose `SignatureRedeemQueue` extends `SignatureQueue` to enable **instant share redemption** from a vault without the usual delay of onchain oracle processing. It leverages **off-chain consensus signatures** conforming to EIP-712 or EIP-1271 to authorize redemptions, allowing trusted users to convert shares to assets in a fast and secure manner. This module provides a **low-latency redemption path** under stronger trust assumptions, useful in environments where responsiveness is critical and participants are whitelisted by a governance consensus. ### Key Features * Off-chain authorized redemptions using signed `Order` messages * Oracle-bound price validation to prevent manipulation * EIP-712 structured data signature verification * Direct burning of shares and asset pulling from the vault and payout * Nonce-based replay protection * Vault hook execution and balance tracking * **No redeem fee** applied (unlike possible fees in `RedeemQueue`) ### Function: `redeem` ```solidity theme={null} function redeem(Order calldata order, IConsensus.Signature[] calldata signatures) external nonReentrant ``` ### Parameters: * `order`: A signed redemption `Order` struct specifying asset amount and recipient * `signatures`: Validator signatures from the consensus group ### Workflow 1. **Validation** via `validateOrder(...)`: * Signature freshness (`deadline`) * Queue and asset correctness * Caller authenticity and correct nonce * Signatures validated by registered `Consensus` contract * Price computed from `ordered` and `requested` values and verified via oracle 2. **Nonce incremented** for the caller to prevent signature reuse 3. **Vault liquid asset check**: * Ensures enough liquidity is available for the redemption * Reverts with `InsufficientAssets` if funds are lacking 4. **Redemption Processing**: * Burns `order.ordered` shares from the user via `shareManager` * Calls `vault.callHook(...)` for any strategy exit logic * Transfers `order.requested` assets to the user * Updates internal vault balance via the `RiskManager` 5. **Event Emitted**: * `OrderExecuted(order, signatures)` confirms successful execution ### Error: `InsufficientAssets` ```solidity theme={null} error InsufficientAssets(uint256 requested, uint256 available); ``` Thrown when the vault does not have enough liquid assets to fulfill the request. Ensures safety during instantaneous exits. # SyncDepositQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/syncdepositqueue ### Overview `SyncDepositQueue` extends [`SyncQueue`](/core-vaults/architecture/queues/syncqueue) to provide **instant deposits**. The user's assets move into the vault and shares are minted to the user in the same transaction, priced at the latest oracle report for the asset. There is no request, no waiting for the next report and no claim step. The call signature matches `DepositQueue.deposit`, so integrations can use the same call for both queue types. ### Key features * Shares minted in the same transaction as the deposit * Pricing from the latest non-suspicious oracle report, no older than `maxAge` * Optional `penaltyD6` discount on the deposit price, kept by existing holders * Vault deposit fee applied, as in `DepositQueue` * Same depositor whitelist check as `DepositQueue` * Deposit hook runs inside the deposit transaction ### Parameters ```solidity theme={null} struct SyncDepositQueueStorage { uint256 penaltyD6; // Price penalty in 1e6 precision (1e4 = 1%) uint32 maxAge; // Maximum age of the oracle report, in seconds } ``` | Parameter | Constraint | Effect | | - | - | - | | `penaltyD6` | `<= 5e5` (50%) | Reduces the shares minted per unit of asset | | `maxAge` | `> 0` and `<= 365 days` | Deposits revert with `StaleReport` once the latest report is older | Parameters are set at creation and can be updated with `setSyncDepositParams(penaltyD6, maxAge)`. The caller must hold `SET_SYNC_DEPOSIT_PARAMS_ROLE` on the vault: ```solidity theme={null} keccak256("queues.SyncDepositQueue.SET_SYNC_DEPOSIT_PARAMS_ROLE") ``` ### Creation Create the queue through the vault with the deposit queue factory version registered for `SyncDepositQueue` (version `2` in the standard deployment): ```solidity theme={null} vault.createQueue( 2, // SyncDepositQueue implementation true, // isDeposit owner, // proxy owner asset, // must be supported by the oracle abi.encode(penaltyD6, maxAge) // uint256, uint32 ); ``` ### Function: `deposit` ```solidity theme={null} function deposit(uint224 assets, address referral, bytes32[] calldata merkleProof) external payable nonReentrant ``` For native ETH (`0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`), `msg.value` must equal `assets` exactly, or the call reverts with `InvalidValue`. For ERC20 assets, approve the queue first. **Execution steps:** 1. Reverts with `ZeroValue` if `assets` is zero. 2. Reverts with `QueuePaused` if the vault has paused the queue. 3. Reverts with `DepositNotAllowed` if `shareManager.isDepositorWhitelisted(caller, merkleProof)` returns `false`. 4. Reads the latest report with `oracle.getReport(asset)`: * Reverts with `InvalidReport` if the report is suspicious or its price is zero. * Reverts with `StaleReport` if `report.timestamp + maxAge < block.timestamp`. 5. Applies the penalty: `priceD18 = report.priceD18 * (1e6 - penaltyD6) / 1e6`. 6. Transfers `assets` from the caller to the queue, then from the queue to the vault. 7. Computes `shares = assets * priceD18 / 1e18`. 8. Computes `feeShares = feeManager.calculateDepositFee(shares)`, mints them to the fee recipient and subtracts them from `shares`. 9. Mints the remaining `shares` to the caller. Reverts with `ZeroValue` if nothing is left. 10. Calls `riskManager.modifyVaultBalance(asset, +assets)`. Reverts with `LimitExceeded` if the deposit would push the vault over its limit, including pending async deposits. 11. Calls `vault.callHook(assets)`, which runs the queue's deposit hook (custom or default) in the vault context. 12. Emits `Deposited(account, referral, assets, shares, feeShares)`. ### Estimating shares To preview the shares a user receives, read the same values the contract uses: ```solidity theme={null} (uint256 penaltyD6, uint32 maxAge) = queue.syncDepositParams(); IOracle.DetailedReport memory report = oracle.getReport(asset); // check: !report.isSuspicious && report.priceD18 != 0 // check: report.timestamp + maxAge >= block.timestamp uint256 priceD18 = report.priceD18 * (1e6 - penaltyD6) / 1e6; uint256 grossShares = assets * priceD18 / 1e18; uint256 userShares = grossShares - feeManager.calculateDepositFee(grossShares); ``` ### Interface compatibility `claimableOf(account)` always returns `0` and `claim(account)` always returns `false`. These stubs exist because `ShareModule.claimShares` and `claimableSharesOf` iterate over all deposit queues. `handleReport` is a no-op and `canBeRemoved()` always returns `true`. ### View functions | Function | Description | | - | - | | `syncDepositParams()` | Returns `(penaltyD6, maxAge)` | | `SET_SYNC_DEPOSIT_PARAMS_ROLE()` | Role required to update parameters | | `vault()` / `asset()` | Owning vault and accepted asset | | `claimableOf(account)` | Always `0` | ### Events ```solidity theme={null} event Deposited(address indexed account, address indexed referral, uint224 assets, uint256 shares, uint256 feeShares); event SyncDepositParamsSet(uint256 penaltyD6, uint32 maxAge); ``` `shares` in `Deposited` is the amount minted to the depositor, after the fee. ### Errors | Error | Reason | | - | - | | `ZeroValue()` | `assets` is zero, zero shares would be minted, or `maxAge` is zero | | `QueuePaused()` | Queue paused by the vault | | `DepositNotAllowed()` | Depositor fails the share manager whitelist check | | `InvalidReport()` | Latest report is suspicious or has price 0 | | `StaleReport()` | Latest report is older than `maxAge` | | `Forbidden()` | Caller lacks `SET_SYNC_DEPOSIT_PARAMS_ROLE` | | `TooLarge()` | `penaltyD6 > 5e5` or `maxAge > 365 days` | | `InvalidValue()` | Native ETH deposit where `msg.value != assets` | ### Design considerations * **Stale-price window.** Deposits are priced at a report that can be up to `maxAge` old. If the real share price has dropped since that report, a deposit mints more shares than it would after the next report. Keep `maxAge` close to the oracle's reporting cadence, and use `penaltyD6` if you need an extra buffer. * **Vault limit.** The vault limit check counts pending async deposits, so a large pending async deposit can make sync deposits revert with `LimitExceeded`. * **Hook execution.** The deposit hook runs in the depositor's transaction, so a reverting hook blocks sync deposits for that queue. # SyncQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/syncqueue ### Overview `SyncQueue` is the abstract base contract for **synchronous queues**: queues that settle a deposit or redemption in the same transaction the user sends. It is not deployable on its own. It is extended by: * [`SyncDepositQueue`](/core-vaults/architecture/queues/syncdepositqueue): instant deposits * [`SyncRedeemQueue`](/core-vaults/architecture/queues/syncredeemqueue): instant redemptions Unlike [`Queue`](/core-vaults/architecture/queues/queue), a sync queue keeps no request history and does not wait for a future oracle report. It prices each action against the **latest stored oracle report** for its asset, which must be non-suspicious and no older than a configured `maxAge`. Unlike [`SignatureQueue`](/core-vaults/architecture/queues/signaturequeue), a sync queue needs no offchain signatures. Any eligible user can call it directly, and the vault's deposit or redeem fee applies. ### How sync queues compare | Property | Async (`DepositQueue` / `RedeemQueue`) | Signature queues | Sync queues | | - | - | - | - | | Settlement | After a later oracle report | Same transaction | Same transaction | | Price source | Report submitted after the request | Signed order, checked against oracle | Latest stored report, within `maxAge` | | Claim step | Yes | No | No | | Offchain approval | No | Consensus signatures | No | | Deposit/redeem fee | Applied | Not applied | Applied | | Extra safeguards | `depositInterval` / `redeemInterval` | Nonce, deadline, signer quorum | `maxAge`, `penaltyD6`, daily limit (redeem only) | ### Storage ```solidity theme={null} struct SyncQueueStorage { address vault; // Vault (ShareModule) that owns this queue address asset; // ERC20 token or native ETH handled by the queue } ``` Storage lives in a custom slot derived with `SlotLibrary.getSlot("SyncQueue", name_, version_)`. ### Initialization ```solidity theme={null} function __SyncQueue_init(address asset_, address vault_) internal ``` * Called by child contracts from `initialize` * Reverts with `ZeroValue` if `asset_` or `vault_` is the zero address * Initializes the reentrancy guard ### Creating a sync queue Sync queues are deployed through the vault's queue factories, like any other queue, with `ShareModule.createQueue`. The standard deployment registers queue implementations in this order: | Factory | Version `0` | Version `1` | Version `2` | | - | - | - | - | | `depositQueueFactory` | `DepositQueue` | `SignatureDepositQueue` | `SyncDepositQueue` | | `redeemQueueFactory` | `RedeemQueue` | `SignatureRedeemQueue` | `SyncRedeemQueue` | Confirm the version on your network with `factory.implementationAt(version)` before you create a queue. ### Interface compatibility Sync queues are registered in `ShareModule` like any other queue, so they implement the methods the vault calls on every queue: * `handleReport(priceD18, timestamp)`: no-op. Oracle reports do not change sync queue state. * `canBeRemoved()`: always `true`, because a sync queue holds no pending requests. * `vault()` and `asset()`: return the configured addresses. * `name()`: returns `"SyncDepositQueue"` or `"SyncRedeemQueue"`. Because `canBeRemoved()` is `true`, `OracleHelper` skips sync redeem queues when it subtracts outstanding redeem demand from vault TVL. ### Errors | Error | Reason | | - | - | | `ZeroValue()` | Zero address at initialization, or a zero amount in a user action | | `QueuePaused()` | The vault has paused this queue with `setQueueStatus` | | `InvalidReport()` | The latest oracle report for the asset is suspicious or has price 0 | # SyncRedeemQueue Source: https://docs.mellow.finance/core-vaults/architecture/queues/syncredeemqueue ### Overview `SyncRedeemQueue` extends [`SyncQueue`](/core-vaults/architecture/queues/syncqueue) to provide **instant redemptions**. The user's shares are burned and the underlying asset is sent to a receiver in the same transaction, priced at the latest oracle report for the asset. There is no request, no batch processing and no claim step. To protect the vault, every redemption must fit within three limits: * **Oracle freshness**: the latest report must be non-suspicious and no older than `maxAge`. * **Liquidity**: the vault and its subvaults must hold enough liquid assets. * **Daily limit**: a rate limit on how many shares can be redeemed over time, shared by all users of the queue. ### Key features * Assets paid out in the same transaction as the redemption * Pricing from the latest non-suspicious oracle report, no older than `maxAge` * Optional `penaltyD6` haircut on redeemed shares, kept by remaining holders * Vault redeem fee applied to every redemption * Liquidity pulled from subvaults through the queue's redeem hook * Leaky-bucket daily limit, denominated in shares ### Parameters ```solidity theme={null} struct SyncRedeemQueueStorage { uint256 penaltyD6; // Share haircut in 1e6 precision (1e4 = 1%) uint32 maxAge; // Maximum age of the oracle report, in seconds uint256 usage; // Stored bucket usage, in shares uint256 dailyLimit; // Bucket capacity, in shares uint256 latestRequestTimestamp; // Last time usage was synchronized } ``` | Parameter | Constraint | Effect | | - | - | - | | `penaltyD6` | `<= 5e5` (50%) | Share haircut applied before the fee and asset conversion | | `maxAge` | `> 0` and `<= 365 days` | Redemptions revert with `StaleReport` once the report is older | | `dailyLimit` | Multiple of `86400` (24 hours) | Capacity of the redemption bucket, in shares | `dailyLimit` must divide evenly by `86400` so that usage decays at an exact whole-share rate per second. A `dailyLimit` of `0` blocks all redemptions through the queue. Parameters are set at creation and can be updated with `setSyncRedeemParams(penaltyD6, maxAge, dailyLimit)`. The caller must hold `SET_SYNC_REDEEM_PARAMS_ROLE` on the vault: ```solidity theme={null} keccak256("queues.SyncRedeemQueue.SET_SYNC_REDEEM_PARAMS_ROLE") ``` Updating parameters first applies the decay accrued so far under the old `dailyLimit`, then stores the new values. ### Creation Create the queue through the vault with the redeem queue factory version registered for `SyncRedeemQueue` (version `2` in the standard deployment): ```solidity theme={null} vault.createQueue( 2, // SyncRedeemQueue implementation false, // isDeposit owner, // proxy owner asset, // must be supported by the oracle abi.encode(penaltyD6, maxAge, dailyLimit) // uint256, uint32, uint256 ); ``` ### Function: `redeem` ```solidity theme={null} function redeem(uint256 shares, address receiver) external nonReentrant ``` No approval is needed. The queue burns shares directly from the caller through the share manager. **Execution steps:** 1. Reverts with `ZeroValue` if `shares` is zero or `receiver` is the zero address. 2. Reverts with `QueuePaused` if the vault has paused the queue. 3. Reads the latest report with `oracle.getReport(asset)`: * Reverts with `InvalidReport` if the report is suspicious or its price is zero. * Reverts with `StaleReport` if `report.timestamp + maxAge < block.timestamp`. 4. Applies the penalty: `sharesToRedeem = shares * (1e6 - penaltyD6) / 1e6`. 5. Computes `feeShares = feeManager.calculateRedeemFee(sharesToRedeem)` and subtracts them from `sharesToRedeem`. 6. Converts to assets: `assets = sharesToRedeem * 1e18 / priceD18`. Reverts with `ZeroValue` if `assets` is zero. 7. Reverts with `InsufficientAssets(assets, available)` if `assets` exceeds `vault.getLiquidAssets()`. 8. Synchronizes the bucket and reverts with `DailyLimitOverflow` if `shares` exceeds the remaining capacity. Otherwise adds `shares` to `usage`. 9. Burns the full `shares` amount from the caller. Share manager restrictions apply, including global lockup, blacklist and burn pause. 10. Mints `feeShares` to the fee recipient. 11. Calls `vault.callHook(assets)`. The redeem hook pulls any missing liquidity from subvaults into the vault, then the vault transfers `assets` to the queue. 12. Sends `assets` to `receiver`. 13. Calls `riskManager.modifyVaultBalance(asset, -assets)`. 14. Emits `Redeemed(account, shares, assets, feeShares)`. The shares removed by the penalty are burned without paying out assets, so their value stays with the remaining holders. ### Liquidity `getLiquidAssets()` returns the result of `vault.getLiquidAssets()` for this queue. The vault resolves the queue's redeem hook (custom or default): * With a hook such as [`BasicRedeemHook`](/core-vaults/architecture/hooks/basicredeemhook), liquidity is the vault's balance plus the balances of subvaults where the asset is allowed. * Without a hook, liquidity is the vault's own balance only. Assets deployed into external protocols do not count until the curator moves them back into the vault or a subvault. ### Daily limit The daily limit is a **leaky bucket** measured in shares, before the penalty and fee: * Each redemption adds its `shares` to `usage`. * `usage` decays linearly at `dailyLimit / 86400` shares per second. * Remaining capacity is `dailyLimit - usage`, after decay. Because capacity refills continuously, up to `2 * dailyLimit` shares can be redeemed within some 24-hour windows: the full bucket at the start, plus a full refill over the following 24 hours. Size risk limits with this burst in mind. The bucket is shared by all users of the queue. One holder can use the whole remaining capacity, and other users then wait for it to refill. ### Estimating assets To preview a redemption, read the same values the contract uses: ```solidity theme={null} (uint256 penaltyD6, uint32 maxAge,,,) = queue.syncRedeemParams(); (, uint256 remaining) = queue.remainingDailyLimit(); uint256 liquid = queue.getLiquidAssets(); IOracle.DetailedReport memory report = oracle.getReport(asset); // check: !report.isSuspicious && report.priceD18 != 0 // check: report.timestamp + maxAge >= block.timestamp // check: shares <= remaining uint256 net = shares * (1e6 - penaltyD6) / 1e6; net -= feeManager.calculateRedeemFee(net); uint256 assets = net * 1e18 / report.priceD18; // check: assets <= liquid ``` The function has no minimum-output argument. Parameters, the oracle report and liquidity can change between the moment you read them and the moment the transaction executes. ### View functions | Function | Description | | - | - | | `syncRedeemParams()` | Returns `(penaltyD6, maxAge, usage, dailyLimit, latestRequestTimestamp)`. `usage` is the stored value, before decay | | `remainingDailyLimit()` | Returns `(usage, remaining)` with decay applied up to the current block | | `getLiquidAssets()` | Liquid assets available for redemption | | `SET_SYNC_REDEEM_PARAMS_ROLE()` | Role required to update parameters | | `vault()` / `asset()` | Owning vault and payout asset | ### Events ```solidity theme={null} event Redeemed(address indexed account, uint256 shares, uint256 assets, uint256 feeShares); event SyncRedeemParamsSet(uint256 penaltyD6, uint32 maxAge, uint256 dailyLimit); ``` `shares` in `Redeemed` is the total amount burned from the account. `assets` is the amount sent to the receiver. ### Errors | Error | Reason | | - | - | | `ZeroValue()` | `shares` is zero, `receiver` is zero, zero assets result, or `maxAge` is zero | | `QueuePaused()` | Queue paused by the vault | | `InvalidReport()` | Latest report is suspicious or has price 0 | | `StaleReport()` | Latest report is older than `maxAge` | | `InsufficientAssets(requested, available)` | Not enough liquid assets in the vault and subvaults | | `DailyLimitOverflow()` | `shares` exceeds the remaining daily capacity | | `Forbidden()` | Caller lacks `SET_SYNC_REDEEM_PARAMS_ROLE` | | `TooLarge()` | `penaltyD6 > 5e5` or `maxAge > 365 days` | | `InvalidDailyLimit()` | `dailyLimit` is not a multiple of `86400` | ### Differences from `RedeemQueue` | Aspect | `RedeemQueue` | `SyncRedeemQueue` | | - | - | - | | Entry point | `redeem(shares)` | `redeem(shares, receiver)` | | Settlement | Report, then `handleBatches`, then `claim` | Same transaction | | Price | First report at least `redeemInterval` after request | Latest report within `maxAge` | | Shares | Locked at request, burned at report | Burned immediately | | Redeem fee | Not charged to the fee recipient | Charged on every redemption | | Cancellation | Not possible | Not applicable | | Rate limit | None | Daily limit in shares | ### Design considerations These points come from the MixBytes audit of `SyncRedeemQueue` (July 2026). The findings were acknowledged as intended behavior. * **Stale-price window.** Redemptions are priced at a report that can be up to `maxAge` old. Keep `maxAge` close to the oracle `timeout`, which is about one day in Mellow's deployments. A `maxAge` shorter than the reporting cadence makes the queue revert with `StaleReport` between reports. `penaltyD6` is expected to stay at `0` in normal operation. * **Priority over async redemptions.** The liquidity check does not reserve assets already owed to priced but unsettled `RedeemQueue` batches. When both queue types are active, sync redemptions take priority, and the curator must keep enough liquidity buffer for both. * **Protocol fee.** Shares burned between reports are not in the supply at the next report, so the time-based protocol fee for that interval is not charged on them. * **Oracle outage.** If reports stop or are flagged suspicious, sync redemptions revert until a valid report is available. The async `RedeemQueue` remains available if the vault has one. * **Native ETH.** The queue accepts ETH from the vault during payout. Do not send ETH to the queue directly, and do not set the queue as `receiver`: those funds cannot be recovered. ### Audit `SyncRedeemQueue` was audited by MixBytes in July 2026. See [Security](/security). # AaveMigrator Source: https://docs.mellow.finance/core-vaults/architecture/utils/aavemigrator ### Overview `AaveMigrator` moves a leveraged Aave position from one subvault to another without unwinding it. It handles one collateral and one debt asset, and moves the position in small steps so that neither subvault's health factor drops below a set minimum. Each step, the target subvault borrows the debt asset and hands it to the source subvault. The source repays that amount of debt and withdraws collateral of equal value straight to the target, and the target supplies it back to Aave. Each side's equity (collateral minus debt) stays roughly the same, and the contract checks the change against a tolerance. The debt and collateral end up in the target. One `AaveMigrator` instance serves one source subvault, one target subvault and one collateral/debt pair. All of these are set in the constructor and cannot be changed. ### Constructor parameters | Parameter | Description | | - | - | | `owner_` | Account allowed to call `migrate` and `kill` | | `pool_` | Aave pool that holds both positions | | `oracle_` | `AaveOracle`-compatible price source used to size the steps | | `sourceSubvault_` | Subvault that currently holds the position | | `targetSubvault_` | Subvault that receives the position | | `collateral_` | Collateral asset | | `debt_` | Debt asset, borrowed at variable rate | | `minAllowedSourceHFD18_` | Lowest health factor allowed for the source, before and after migration (1e18 precision) | | `minAllowedTargetHFD18_` | Lowest health factor allowed for the target, before and after migration (1e18 precision) | | `minAllowedMigratingDebt_` | Smallest amount of debt, in debt asset units, that a single `migrate` call may move | | `maxAllowedError_` | Largest tolerated value difference, in the Aave oracle's base currency units | The constructor reads the collateral aToken, debt aToken and variable debt token from the pool. All values are exposed as public immutables. `maxAllowedError_` uses the Aave oracle's base currency. On USD markets that has 8 decimals, so `1e6` is \$0.01. ### Requirements * The `AaveMigrator` contract calls both subvaults through `Subvault.call`, so it must hold the verifier `CALLER_ROLE` on the vault. * It always sends an `ONCHAIN_COMPACT` verification payload, so the calls below must be allowed with `allowCalls` in each subvault's verifier. A Merkle-only permission is not enough. | Subvault | Allowed calls | | - | - | | Source | `debt.approve`, `pool.repay`, `pool.withdraw` | | Target | `pool.borrow`, `debt.transfer`, `collateral.approve`, `pool.supply` | * The target must already have borrowing capacity in Aave, because every step starts with a borrow from the target. In practice, the target needs some collateral supplied in advance. * If the position relies on an Aave e-mode category, enable the same category on the target first. Otherwise its borrowing capacity and health factor are lower. ### Function: `migrate` ```solidity theme={null} function migrate(uint256 maxUtilizationD18, uint256 percentageD6) external ``` Owner only. Moves `percentageD6` of the source's current debt, together with collateral of the same value, to the target. | Argument | Range | Meaning | | - | - | - | | `maxUtilizationD18` | `> 0` and `<= 1e18` | Share of the target's available borrowing capacity used per step | | `percentageD6` | `> 0` and `<= 1e6` | Share of the source's current debt to move. `1e6` moves all of it | **Execution steps:** 1. Reverts with `InvalidMaxUtilizationD18` or `InvalidPercentageD6` if an argument is out of range, and with `AlreadyMigrated` if the migrator has finished or been killed. 2. Calls `calculateSteps` to get the number of steps, the debt and collateral per step and the expected remaining source debt. 3. Checks that both health factors are at or above their minimums and records each subvault's equity. 4. Runs each step: 1. The target borrows `debtPerStep` of the debt asset at variable rate. 2. The target transfers it to the source. 3. The source approves the pool and repays `debtPerStep`. 4. The source withdraws `collateralPerStep` of collateral, sent straight to the target. 5. The target approves the pool and supplies the collateral. 5. Checks both health factors again and adds up how much each subvault's equity changed. Reverts with `CumulativeErrorTooHigh` if the total exceeds `maxAllowedError`. 6. Reverts with `TooHighDeviation` if the source's remaining debt differs from the expected amount by more than `maxAllowedError` in value. 7. If the source's remaining debt is worth `maxAllowedError` or less, marks the migration as finished and emits `Migrated`. Otherwise emits `PartiallyMigrated`, and the owner can call `migrate` again. Before each `approve`, the subvault resets any existing allowance to 0, which some tokens require. ### Function: `calculateSteps` ```solidity theme={null} function calculateSteps(uint256 maxUtilizationD18, uint256 percentageD6) external view returns (uint256 iterations, uint256 debtPerStep, uint256 collateralPerStep, uint256 expectedDebtAfter) ``` Previews a migration with the same checks `migrate` uses: 1. `debtToMigrate` is `percentageD6` of the source's variable debt. Reverts with `InsufficientDebt` if it is below `minAllowedMigratingDebt`. 2. Reverts with `InvalidDebtAfterMigration` if the leftover debt is worth more than `maxAllowedError` but is below `minAllowedMigratingDebt`. That would leave debt too large to ignore and too small to migrate later. 3. The largest step is the target's available borrowing capacity times `maxUtilizationD18`, converted to the debt asset. It is also capped by the debt asset liquidity available in the Aave reserve. 4. `iterations` is `debtToMigrate` divided by the largest step, rounded up. Reverts with `TooManyIterations` if it exceeds `MAX_ALLOWED_ITERATIONS` (30) or no borrowing is possible. 5. `debtPerStep` is `debtToMigrate / iterations`, and `collateralPerStep` is collateral of the same value at oracle prices. Reverts with `NotEnoughCollateral` if the source does not hold enough collateral for all steps. Call it before `migrate` to choose a `maxUtilizationD18` and `percentageD6` that fit within 30 steps. ### Function: `checkHFAndGetEquity` ```solidity theme={null} function checkHFAndGetEquity(address holder, uint256 minAllowedHealthFactorD18) external view returns (uint256) ``` Reverts with `HealthFactorIsTooLow` if `holder`'s Aave health factor is below the minimum. Otherwise returns its equity, total collateral minus total debt, in the Aave base currency. ### Function: `kill` Owner only. Permanently disables `migrate` and emits `Killed`. Use it to retire the migrator, for example after a partial migration that you do not want to continue. ### Events ```solidity theme={null} event Migrated(uint256 indexed timestamp, address indexed caller, uint256 indexed cumulativeError); event PartiallyMigrated(uint256 indexed timestamp, address indexed caller, uint256 indexed cumulativeError, uint256 percentageD6); event Killed(uint256 indexed timestamp, address indexed caller); ``` `cumulativeError` is the total equity change across both subvaults, in the Aave base currency. ### Errors | Error | Reason | | - | - | | `AlreadyMigrated()` | Migration finished, or the migrator was killed | | `InvalidMaxUtilizationD18()` | `maxUtilizationD18` is 0 or above `1e18` | | `InvalidPercentageD6()` | `percentageD6` is 0 or above `1e6` | | `InsufficientDebt()` | Debt to migrate is below `minAllowedMigratingDebt` | | `InvalidDebtAfterMigration()` | Leftover debt is too large to ignore and too small to migrate later | | `TooManyIterations(iterations)` | More than 30 steps needed, or no borrowing capacity or liquidity | | `NotEnoughCollateral()` | Source does not hold enough collateral for the planned steps | | `HealthFactorIsTooLow()` | A health factor is below its minimum, before or after migration | | `CumulativeErrorTooHigh()` | Total equity change exceeds `maxAllowedError` | | `TooHighDeviation()` | Remaining source debt differs from the expected amount by too much | | `ERC20TransferFailed()` | The debt token `transfer` returned `false` | | `ERC20ApproveFailed()` | A token `approve` returned `false` | ### Notes * Assets move between the subvaults through Aave, not through `Vault.pushAssets` or `Vault.pullAssets`. The `RiskManager` subvault balances are not updated by a migration. * The vault's total equity stays the same within `maxAllowedError`, so a migration should not change the vault's reported value. Only the split between subvaults changes. # SwapModule Source: https://docs.mellow.finance/core-vaults/architecture/utils/swapmodule ### Overview `SwapModule` is a helper contract that lets curators swap a subvault's assets under onchain guardrails. Each module serves one subvault. The subvault pushes assets into the module, an authorized caller swaps them through an approved router or a CoW Swap limit order, and the subvault pulls the output back. Every swap is checked against an external price oracle, so the curator cannot accept a price worse than a configured tolerance. ### Roles `SwapModule` uses `MellowACL`, so roles are granted on the module itself. Three roles are granted to **token or router addresses**, not to people: | Role | Granted to | Allows | | - | - | - | | `DEFAULT_ADMIN_ROLE` | Admin account | Managing roles and calling `setOracle` | | `CALLER_ROLE` | Curator accounts | Calling `swap`, `setCowswapApproval`, `createLimitOrder`, `invalidateOrder` | | `SET_SLIPPAGE_ROLE` | Accounts | Setting the default and per-pair price multipliers | | `TOKEN_IN_ROLE` | Token addresses | Token can be sold | | `TOKEN_OUT_ROLE` | Token addresses | Token can be bought | | `ROUTER_ROLE` | Router addresses | Router can execute swaps | Role IDs follow the pattern `keccak256("utils.SwapModule.")`. ### Initialization The module is deployed through a factory and initialized with: ```solidity theme={null} abi.encode( address admin, address subvault, address oracle, // AaveOracle-compatible price source uint256 defaultMultiplier, // 1e8 precision, between 0.9e8 and 1.1e8 address[] holders, bytes32[] roles // roles[i] is granted to holders[i] ) ``` The constructor sets immutable addresses for the CoW Swap settlement contract, the CoW Swap vault relayer and WETH. ### Moving assets | Function | Caller | Effect | | - | - | - | | `pushAssets(asset, value)` | Subvault | Pulls `value` of `asset` from the subvault into the module | | `pullAssets(asset, value)` | Subvault | Sends `value` of `asset` from the module back to the subvault | Only the configured subvault can call these. In practice the curator triggers them through `Subvault.call`, so the subvault's verifier must allow those calls, and the `approve` that precedes a push. For native ETH, `pushAssets` must be called with `msg.value == value`. ### Price check Before any swap or limit order, `checkParams` validates the `Params` struct: ```solidity theme={null} struct Params { address tokenIn; address tokenOut; uint256 amountIn; uint256 minAmountOut; uint256 deadline; } ``` * `tokenIn` holds `TOKEN_IN_ROLE` and `tokenOut` holds `TOKEN_OUT_ROLE`. * `amountIn` is non-zero and not more than the module's balance of `tokenIn`. * `tokenIn` differs from `tokenOut`, and `deadline` has not passed. * `minAmountOut` is at least the oracle value of `amountIn`, adjusted by the multiplier: ```solidity theme={null} oracleMinAmount = evaluate(tokenIn, tokenOut, amountIn) * multiplier / 1e8; ``` `evaluate` converts with `getAssetPrice` from the oracle and adjusts for token decimals. Native ETH is priced as WETH. The multiplier is the per-pair value set with `setCustomMultiplier(tokenIn, tokenOut, m)`, or `defaultMultiplier` if none is set. Multipliers must stay between `0.9e8` and `1.1e8`. For example, `0.995e8` allows a result up to 0.5% below the oracle value. Failed checks revert with `Forbidden(reason)`, where `reason` names the failing check. ### Router swaps ```solidity theme={null} function swap(Params calldata params, address router, bytes calldata data) external returns (bytes memory) ``` Callable by `CALLER_ROLE`. 1. Runs `checkParams(params)` and checks that `router` holds `ROUTER_ROLE`. 2. For ERC20 input, approves `router` for exactly `amountIn`, calls it with `data`, then resets the approval to 0. For native ETH input, calls the router with `amountIn` as value. 3. Measures the module's `tokenOut` balance increase and reverts if it is below `minAmountOut`. 4. Emits `Swap(params, router, amountOut)`. The output stays in the module until the subvault calls `pullAssets`. ### CoW Swap limit orders For CoW Swap, the module signs orders onchain with pre-signatures. * `setCowswapApproval(asset, amount)`: approves the CoW Swap vault relayer to spend `asset`. The asset must hold `TOKEN_IN_ROLE` and cannot be native ETH. * `createLimitOrder(params, order, orderUid)`: runs `checkParams` and `checkCowswapOrder`, then calls `setPreSignature(orderUid, true)` on the settlement contract. * `invalidateOrder(orderUid)`: cancels an order on the settlement contract. `checkCowswapOrder` requires the order to be a sell order that uses ERC20 balances, with the module as receiver. Its sell token, buy token, sell amount, buy amount and `validTo` must match `params`, and `orderUid` must match the order hash. ETH is not supported in limit orders. ### Configuration functions | Function | Role | | - | - | | `setOracle(oracle)` | `DEFAULT_ADMIN_ROLE` | | `setDefaultMultiplier(multiplier)` | `SET_SLIPPAGE_ROLE` | | `setCustomMultiplier(tokenIn, tokenOut, m)` | `SET_SLIPPAGE_ROLE` | ### Events ```solidity theme={null} event AssetsPushed(address indexed asset, uint256 value); event AssetsPulled(address indexed asset, uint256 value); event Swap(Params params, address router, uint256 amountOut); event CowswapApprovalSet(address indexed asset, uint256 amount); event LimitOrderCreated(Params params, bytes orderUid); event LimitOrderInvalidated(bytes orderUid); event DefaultMultiplierSet(uint256 indexed multiplier); event CustomMultiplierSet(address indexed tokenIn, address indexed tokenOut, uint256 indexed multiplier); event OracleSet(address indexed oracle); ``` ### Price sources The oracle must implement `getAssetPrice(address)` as `AaveOracle` does. For assets without a Chainlink feed, Mellow uses custom `AaveOracle` deployments backed by [`PermissionedChainlinkOracle`](/core-vaults/architecture/oracles/permissionedchainlinkoracle) feeds. ### Deployments `SwapModule` addresses for live vaults are listed on the deployment pages, for example [Lido Earn](/lido-earn/deployments) and [strETH](/strategy-vault/streth-deployment). # Vaults Source: https://docs.mellow.finance/core-vaults/architecture/vaults/index In this directory, you will find a detailed per-contract overview of the "Vaults" contract category, including the following contracts: [Vault](/core-vaults/architecture/vaults/vault) [Subvault](/core-vaults/architecture/vaults/subvault) [VaultConfigurator](/core-vaults/architecture/vaults/vaultconfigurator) # Subvault Source: https://docs.mellow.finance/core-vaults/architecture/vaults/subvault ## Overview The `Subvault` contract represents a modular, permissioned vault component designed to manage delegated asset strategies within a parent `Vault`. It enables curated logic for permissioned calls and delegated strategy execution without exposing external deposit or redemption interfaces. This contract combines callable and verifiable logic to serve as a secure, controlled execution unit within a system. ## Inheritance Structure ```solidity theme={null} contract Subvault is IFactoryEntity, CallModule, SubvaultModule ``` The `Subvault` inherits: * `CallModule`: Enables arbitrary low-level calls to external contracts (used by curator of the vault), and verification through a verifier module. * `SubvaultModule`: Handles vault linkage and liquidity handling. * `IFactoryEntity`: Standard initialization interface for factory deployment compatibility. The constructor explicitly calls: ```solidity theme={null} VerifierModule(name_, version_) SubvaultModule(name_, version_) ``` This indicates that both modules rely on deterministic storage and versioned deployment identifiers via `SlotLibrary`. ## Constructor ```solidity theme={null} constructor(string memory name_, uint256 version_) ``` ### Parameters: * `name_`: A unique string identifier for the deployment (e.g., "Mellow"). * `version_`: A version number used to derive storage slots and allow upgradeable logic. ### Behavior: Passes the `name_` and `version_` arguments into the constructors of `VerifierModule` and `SubvaultModule`. ## External Functions ### `initialize` ```solidity theme={null} function initialize(bytes calldata initParams) external initializer ``` Initializes the subvault contract. This function can only be called once due to the `initializer` modifier. ### Parameters: * `initParams`: ABI-encoded as `(address verifier_, address vault_)` ### Initialization Steps: 1. Decodes `verifier_` and `vault_` from the calldata. 2. Calls `__VerifierModule_init(verifier_)` to link the external verifier (used for strategy proof or access control). 3. Calls `__SubvaultModule_init(vault_)` to register this subvault with the parent vault. 4. Emits the `Initialized(initParams)` event for transparency. ## Design Notes * **Modular Strategy Execution**: The `CallModule` enables arbitrary external calls, useful for delegating assets into other protocols or yield strategies. * **Trust-Minimized Calls**: External strategy actions are gated via a `VerifierModule`, which can enforce logic like off-chain signatures or time-based constraints. * **Parent Vault Registration**: Initialization ensures the `vault` address is securely set once and governs access and lifecycle. * **Upgradeable Architecture**: Follows the shared pattern of using deterministic storage slots (via `SlotLibrary`) to remain safely upgradeable and composable. ## Events ### `Initialized(bytes data)` Emitted once after a successful `initialize` call. Contains the raw ABI-encoded input for auditing or debugging. # Vault Source: https://docs.mellow.finance/core-vaults/architecture/vaults/vault ## Overview The `Vault` contract is the central entry point in the Flexible Vault system. It composes three foundational modules: * `ACLModule`: Role-based access control. * `ShareModule`: Management of user-facing shares, including deposit and redemption processes. * `VaultModule`: Subvault management. This contract allows secure, extensible, and upgradeable vault implementations by coordinating all external and internal interactions within the system. It is typically instantiated through `Factory` or `VaultConfigurator` and initialized with all the required components and role assignments in a single atomic transaction. ## Inheritance Structure ```solidity theme={null} contract Vault is IFactoryEntity, VaultModule, ShareModule, ACLModule ``` The contract inherits three modules: * `ACLModule`: Admin and permission management * `ShareModule`: Deposits, redemptions, share management * `VaultModule`: Subvault delegation control It also implements the `IFactoryEntity` interface for standard factory-based deployment patterns. ## Constructor ```solidity theme={null} constructor( string memory name_, uint256 version_, address depositQueueFactory_, address redeemQueueFactory_, address subvaultFactory_, address verifierFactory_ ) ``` ### Parameters: * `name_`: Unique name identifier for the vault instance * `version_`: Configuration version of the vault * `depositQueueFactory_`: Address of the factory used to deploy deposit queues * `redeemQueueFactory_`: Address of the factory used to deploy redemption queues * `subvaultFactory_`: Address of the factory used to deploy subvaults * `verifierFactory_`: Address of the factory for deploying verifier contracts ### Behavior: Passes these arguments to the parent module constructors: * `ACLModule(name_, version_)` * `ShareModule(name_, version_, depositQueueFactory_, redeemQueueFactory_)` * `VaultModule(name_, version_, subvaultFactory_, verifierFactory_)` ## Structs ### `RoleHolder` ```solidity theme={null} struct RoleHolder { bytes32 role; address holder; } ``` Used to batch-assign multiple roles during initialization. Each entry maps a role identifier to a designated address. ## External Functions ### `initialize` ```solidity theme={null} function initialize(bytes calldata initParams) external initializer ``` Initializes the vault instance. Can only be called once due to the `initializer` modifier. ### `initParams` structure (ABI-encoded): ```solidity theme={null} ( address admin_, address shareManager_, address feeManager_, address riskManager_, address oracle_, address defaultDepositHook_, address defaultRedeemHook_, uint256 queueLimit_, RoleHolder[] roleHolders ) ``` ### Initialization Logic: * Calls `__ACLModule_init(admin_)` to configure the default admin. * Calls `__ShareModule_init(...)` to link share management and hook modules. * Calls `__VaultModule_init(riskManager_)` to initialize risk management. * Iterates over `roleHolders` and grants each role using `_grantRole(...)`. * Emits `Initialized(initParams)`. ## Design Notes * **Modular Composition**: The vault is composed by inheriting three upgradeable modules, enabling reuse and flexible configuration. * **Factory-Compatible**: The contract is factory-deployable and supports atomic configuration during creation. * **Centralized Control Layer**: Acts as a trusted coordinator for hooks, queues, shares, and strategy logic. * **Role Assignment**: Enables full delegation of operational control via batched `RoleHolder` entries. * **Upgradeable and Isolated**: Each module manages its own storage via deterministic slots (`SlotLibrary`) to support safe upgrades. ## Events ### `Initialized(bytes data)` Emitted after successful initialization. Includes all parameters passed for transparency. # VaultConfigurator Source: https://docs.mellow.finance/core-vaults/architecture/vaults/vaultconfigurator ## Overview The `VaultConfigurator` contract provides a streamlined and modular deployment mechanism for setting up a new `Vault` instance and its associated managers. It orchestrates the creation and initialization of the following components: * `Vault` * `ShareManager` * `FeeManager` * `RiskManager` * `Oracle` It ensures that all components are correctly wired together by setting appropriate references between them. ## Purpose This contract is designed to be used by an actor that need to deploy and configure fully functional vaults in a deterministic and upgradeable way, using versioned module factories. ## Contract Structure ### State Variables ```solidity theme={null} IFactory public immutable shareManagerFactory; IFactory public immutable feeManagerFactory; IFactory public immutable riskManagerFactory; IFactory public immutable oracleFactory; IFactory public immutable vaultFactory; ``` Each of these holds a reference to a factory contract responsible for creating a specific type of contract. ### Constructor ```solidity theme={null} constructor( address shareManagerFactory_, address feeManagerFactory_, address riskManagerFactory_, address oracleFactory_, address vaultFactory_ ) ``` Initializes the configurator with references to module factories. ## InitParams Struct ```solidity theme={null} struct InitParams { uint256 version; address proxyAdmin; address vaultAdmin; uint256 shareManagerVersion; bytes shareManagerParams; uint256 feeManagerVersion; bytes feeManagerParams; uint256 riskManagerVersion; bytes riskManagerParams; uint256 oracleVersion; bytes oracleParams; address defaultDepositHook; address defaultRedeemHook; uint256 queueLimit; Vault.RoleHolder[] roleHolders; } ``` ### Fields: * `version`: Version of the `Vault` implementation to deploy. * `proxyAdmin`: Address to be set as `ProxyAdmin` for upgradeable proxies. * `vaultAdmin`: Address to be set as the vault's owner (admin). * `_Version`: Specific implementation version to use for each module (used in the corresponding factory). * `_Params`: ABI-encoded initialization parameters for each module. * `defaultDepositHook`: Address of the default deposit hook to attach to queues. * `defaultRedeemHook`: Address of the default redeem hook to attach to queues. * `queueLimit`: Maximum number of queued operations per deposit/redeem queue. * `roleHolders`: List of role assignments for vault-level access control. ## External Functions ### `create` ```solidity theme={null} function create(InitParams calldata params) external returns ( address shareManager, address feeManager, address riskManager, address oracle, address vault ) ``` ### Description: Creates and initializes a new vault instance along with all dependent modules using the provided factory addresses and parameters. ### Steps: 1. **Deploy ShareManager**: * Uses `shareManagerFactory` to deploy a versioned `ShareManager` proxy. 2. **Deploy FeeManager**: * Uses `feeManagerFactory` to deploy a versioned `FeeManager`. 3. **Deploy RiskManager**: * Uses `riskManagerFactory` to deploy a versioned `RiskManager`. 4. **Deploy Oracle**: * Uses `oracleFactory` to deploy a versioned `Oracle`. 5. **Deploy Vault**: * Prepares encoded initialization calldata and calls `vaultFactory.create()` with the version and proxy admin. 6. **Post-deployment Wiring**: * Sets the `vault` address in each of the deployed components using: * `IShareManager(shareManager).setVault(vault)` * `IRiskManager(riskManager).setVault(vault)` * `IOracle(oracle).setVault(vault)` ### Returns: * `shareManager`: Address of the deployed share manager contract * `feeManager`: Address of the deployed fee manager contract * `riskManager`: Address of the deployed risk manager contract * `oracle`: Address of the deployed oracle contract * `vault`: Address of the newly created vault # Core Deployments Source: https://docs.mellow.finance/core-vaults/core-deployments Contract addresses for all Core Vault implementations and factories across all chains with implementations and factories. | Factory implementation | [0x0000000397b71C8f3182Fd40D247330D218fdC72](https://etherscan.io/address/0x0000000397b71C8f3182Fd40D247330D218fdC72) | | - | - | | Factory factory | [0x0000000f9686896836C39cf721141922Ce42639f](https://etherscan.io/address/0x0000000f9686896836C39cf721141922Ce42639f) | | Consensus factory | [0xaEEB06CBd91A18b51a2D30b61477eAeE3a9633C3](https://etherscan.io/address/0xaEEB06CBd91A18b51a2D30b61477eAeE3a9633C3) | | Consensus implementation | [0x0000000167598d2C78E2313fD5328E16bD9A0b13](https://etherscan.io/address/0x0000000167598d2C78E2313fD5328E16bD9A0b13) | | DepositQueue factory | [0xBB92A7B9695750e1234BaB18F83b73686dd09854](https://etherscan.io/address/0xBB92A7B9695750e1234BaB18F83b73686dd09854) | | DepositQueue implementation | [0x00000006dA9f179BFE250Dd1c51cD2d3581930c8](https://etherscan.io/address/0x00000006dA9f179BFE250Dd1c51cD2d3581930c8) | | SyncDepositQueue implementation | [0x000000000b98f77a017b5d3468400c5C597a3Bde](https://etherscan.io/address/0x000000000b98f77a017b5d3468400c5C597a3Bde) | | SignatureDepositQueue implementation | [0x00000003887dfBCEbD1e4097Ad89B690de7eFbf9](https://etherscan.io/address/0x00000003887dfBCEbD1e4097Ad89B690de7eFbf9) | | FeeManager factory | [0xF7223356819Ea48f25880b6c2ab3e907CC336D45](https://etherscan.io/address/0xF7223356819Ea48f25880b6c2ab3e907CC336D45) | | FeeManager implementation | [0x0000000dE74e5D51651326E0A3e1ACA94bEAF6E1](https://etherscan.io/address/0x0000000dE74e5D51651326E0A3e1ACA94bEAF6E1) | | Oracle factory | [0x0CdFf250C7a071fdc72340D820C5C8e29507Aaad](https://etherscan.io/address/0x0CdFf250C7a071fdc72340D820C5C8e29507Aaad) | | Oracle implementation | [0x0000000F0d3D1c31b72368366A4049C05E291D58](https://etherscan.io/address/0x0000000F0d3D1c31b72368366A4049C05E291D58) | | RedeemQueue factory | [0xfe76b5fd238553D65Ce6dd0A572C0fda629F8421](https://etherscan.io/address/0xfe76b5fd238553D65Ce6dd0A572C0fda629F8421) | | RedeemQueue implementation | [0x000000000c139266BA06170Ed1DeacA6d11903c1](https://etherscan.io/address/0x000000000c139266BA06170Ed1DeacA6d11903c1) | | SyncRedeemQueue implementation | [0x0000000038801c7281284f8f68b80b679f64a074](https://etherscan.io/address/0x0000000038801c7281284f8f68b80b679f64a074#code) | | SignatureRedeemQueue implementation | [0x0000000b2082667589A16c4cF18e9f923781c471](https://etherscan.io/address/0x0000000b2082667589A16c4cF18e9f923781c471) | | RiskManager factory | [0xa51E4FA916b939Fa451520D2B7600c740d86E5A0](https://etherscan.io/address/0xa51E4FA916b939Fa451520D2B7600c740d86E5A0) | | RiskManager implementation | [0x0000000714cf2851baC1AE2f41871862e9D216fD](https://etherscan.io/address/0x0000000714cf2851baC1AE2f41871862e9D216fD) | | ShareManager factory | [0x952f39AA62E94db3Ad0d1C7D1E43C1a8519E45D8](https://etherscan.io/address/0x952f39AA62E94db3Ad0d1C7D1E43C1a8519E45D8) | | TokenizedShareManager implementation | [0x0000000E8eb7173fA1a3ba60eCA325bcB6aaf378](https://etherscan.io/address/0x0000000E8eb7173fA1a3ba60eCA325bcB6aaf378) | | BasicShareManager implementation | [0x00000005564AAE40D88e2F08dA71CBe156767977](https://etherscan.io/address/0x00000005564AAE40D88e2F08dA71CBe156767977) | | Subvault factory | [0x75FE0d73d3C64cdC1C6449D9F977Be6857c4d011](https://etherscan.io/address/0x75FE0d73d3C64cdC1C6449D9F977Be6857c4d011) | | Subvault implementation | [0x0000000E535B4E063f8372933A55470e67910a66](https://etherscan.io/address/0x0000000E535B4E063f8372933A55470e67910a66) | | Verifier factory | [0x04B30b1e98950e6A13550d84e991bE0d734C2c61](https://etherscan.io/address/0x04B30b1e98950e6A13550d84e991bE0d734C2c61) | | Verifier implementation | [0x000000047Fc878662006E78D5174FB4285637966](https://etherscan.io/address/0x000000047Fc878662006E78D5174FB4285637966) | | Vault factory | [0x4E38F679e46B3216f0bd4B314E9C429AFfB1dEE3](https://etherscan.io/address/0x4E38F679e46B3216f0bd4B314E9C429AFfB1dEE3) | | Vault implementation | [0x0000000615B2771511dAa693aC07BE5622869E01](https://etherscan.io/address/0x0000000615B2771511dAa693aC07BE5622869E01) | | SwapModule Factory | [0xE3575055a24d8642DFA3a51ec766Ef2db2671659](https://etherscan.io/address/0xE3575055a24d8642DFA3a51ec766Ef2db2671659) | | SwapModule implementation | [0x00000000d681E85e5783588f87A9573Cb97Eda01](https://etherscan.io/address/0x00000000d681E85e5783588f87A9573Cb97Eda01) | | BitmaskVerifier | [0x0000000263Fb29C3D6B0C5837883519eF05ea20A](https://etherscan.io/address/0x0000000263Fb29C3D6B0C5837883519eF05ea20A) | | EigenLayerVerifier factory | [0x77A83AcBf7A6df20f1D681b4810437d74AE790F8](https://etherscan.io/address/0x77A83AcBf7A6df20f1D681b4810437d74AE790F8) | | EigenLayerVerifier | [0x00000003F82051A8B2F020B79e94C3DC94E89B81](https://etherscan.io/address/0x00000003F82051A8B2F020B79e94C3DC94E89B81) | | ERC20Verifier factory | [0x2e234F4E1b7934d5F4bEAE3fF2FDC109f5C42F1d](https://etherscan.io/address/0x2e234F4E1b7934d5F4bEAE3fF2FDC109f5C42F1d) | | ERC20Verifier | [0x00000009207D366cBB8549837F8Ae4bf800Af2D6](https://etherscan.io/address/0x00000009207D366cBB8549837F8Ae4bf800Af2D6) | | SymbioticVerifier factory | [0x41C443F10a92D597e6c9E271140BC94c10f5159F](https://etherscan.io/address/0x41C443F10a92D597e6c9E271140BC94c10f5159F) | | SymbioticVerifier | [0x00000000cBC6f5d4348496FfA22Cf014b9DA394B](https://etherscan.io/address/0x00000000cBC6f5d4348496FfA22Cf014b9DA394B) | | VaultConfigurator | [0x000000028be48f9E62E13403480B60C4822C5aa5](https://etherscan.io/address/0x000000028be48f9E62E13403480B60C4822C5aa5) | | BasicRedeeHook | [0x0000000637f1b1ccDA4Af2dB6CDDf5e5Ec45fd93](https://etherscan.io/address/0x0000000637f1b1ccDA4Af2dB6CDDf5e5Ec45fd93) | | RedirectingDepositHook | [0x00000004d3B17e5391eb571dDb8fDF95646ca827](https://etherscan.io/address/0x00000004d3B17e5391eb571dDb8fDF95646ca827) | | LidoDepositHook | [0x000000065d1A7bD71f52886910aaBE6555b7317c](https://etherscan.io/address/0x000000065d1A7bD71f52886910aaBE6555b7317c) | | OracleHelper | [0x000000005F543c38d5ea6D0bF10A50974Eb55E35](https://etherscan.io/address/0x000000005F543c38d5ea6D0bF10A50974Eb55E35) | | BurnableTokenizedShareManager | [0x000000000c79D2B5cD58AE545afc83030233D7B6](https://etherscan.io/address/0x000000000c79D2B5cD58AE545afc83030233D7B6) | | Contract Address | Address | | - | - | | Factory | `0x0000000072BAfCeAff1AD0237Ea58f06cfc4467F` | | FactoryFactory | `0x00000000741292C88f9fF5050b07051C4f592EBf` | | ConsensusFactory | `0xAfef40968b5304135677f0C89203948e1A145105` | | DepositQueueFactory | `0xF429ba2a8437E7de85078CF7481E8Ad52df7E58c` | | RedeemQueueFactory | `0xe08dc488bD6756323F8bf478869529D03db627ef` | | OracleFactory | `0x727c295b5D99b15280Ca8736b6F97ABA6aEd0E88` | | FeeManagerFactory | `0x52d56c20B0C8d403888880d0A1610e5ed17addA8` | | RiskManagerFactory | `0x9885215ef8DB25C87466E73018061e532784D716` | | ShareManagerFactory | `0xDA2a7aE07B6803feF9d95E47Ab83c8a5A09929F0` | | SubvaultFactory | `0xA64e324DFF04e3C0613ff0706867868C7b370a45` | | VaultFactory | `0xBBCD2aC50aF2EA12Cc9cb7B16dBDa85859BeB3da` | | VerifierFactory | `0x9fBAF5AEB9F52bA57E1cC1D3050eac6d75Df8ae7` | | ERC20VerifierFactory | `0x711F6236e325634AA8c1F692b5312bfF3A8558D0` | | AccountFactory | `0x870DB41df0905cc5a790f6582a3dA99A4A33F923` | | SwapModuleFactory | `0xC5a52E4bB718Dfe86938e5cB967362EdA1E62698` | | Consensus | `0x000000007e6b679B9196a1609e5Bc2405eDFd6Aa` | | DepositQueue | `0x00000000B2d2373aAF1C370cFE4e1Ee8BDE7C546` | | SignatureDepositQueue | `0x000000000Af33501e5BDAF9B481Ad2712a024727` | | SyncDepositQueue | `0x000000001CC8c3E40856E956db870095EF6C98bd` | | FeeManager | `0x00000000C18039E1F415fe07C33A316232238648` | | Oracle | `0x000000009adE4dAE1f868775A3f087945983f062` | | RedeemQueue | `0x0000000045d70ee8145135f08309fF5B1A63d43F` | | SignatureRedeemQueue | `0x000000008D14Ef3658805765107d9F12776f4138` | | RiskManager | `0x00000000CC26BC741E75B181738Ac2B16156179b` | | TokenizedShareManager | `0x00000000861e8B90B81f35C18cA14858Cc91d1Df` | | BasicShareManager | `0x00000000e5F0cddA56447b2a29e2847A52c8725D` | | BurnableTokenizedShareManager | `0x00000000C534B8680e3aa7165DeDc3Ab8781f602` | | Subvault | `0x00000000CA30010B8417f791250AE221FdaD5920` | | Verifier | `0x000000007e86a96e279662108cc19bA4c32EdE3C` | | ERC20Verifier | `0x00000000ACD80376E999Af8c424e5e33BD224A08` | | MellowAccountV1 | `0x00000000860913f37fab81ce8ce4E5BD1f664482` | | SwapModule | `0x00000000015fa996bCA8c842AFEdC334616F283A` | | Vault | `0x0000000070f44289ec5ea3E5972f058f75B29801` | | BitmaskVerifier | `0x0000000009E9368ad21fc19DCE1cFcf9Af6dE339` | | VaultConfigurator | `0x0000000005a67199ABE0f9C995EAB9DaDfA31Ccd` | | BasicRedeemHook | `0x00000000176dD23550c3845746b2036E90DC5912` | | RedirectingDepositHook | `0x0000000024ABbd08686Abb2987831dEa88eF1180` | | OracleHelper | `0x000000007d2552AD746Af5c13f91B5e72f97c2B7` | | Contract Address | Address | | - | - | | Factory | 0x0000000072BAfCeAff1AD0237Ea58f06cfc4467F | | FactoryFactory | 0x00000000741292C88f9fF5050b07051C4f592EBf | | ConsensusFactory | 0xAfef40968b5304135677f0C89203948e1A145105 | | DepositQueueFactory | 0xF429ba2a8437E7de85078CF7481E8Ad52df7E58c | | RedeemQueueFactory | 0xe08dc488bD6756323F8bf478869529D03db627ef | | OracleFactory | 0x727c295b5D99b15280Ca8736b6F97ABA6aEd0E88 | | FeeManagerFactory | 0x52d56c20B0C8d403888880d0A1610e5ed17addA8 | | RiskManagerFactory | 0x9885215ef8DB25C87466E73018061e532784D716 | | ShareManagerFactory | 0xDA2a7aE07B6803feF9d95E47Ab83c8a5A09929F0 | | SubvaultFactory | 0xA64e324DFF04e3C0613ff0706867868C7b370a45 | | VaultFactory | 0xBBCD2aC50aF2EA12Cc9cb7B16dBDa85859BeB3da | | VerifierFactory | 0x9fBAF5AEB9F52bA57E1cC1D3050eac6d75Df8ae7 | | ERC20VerifierFactory | 0x711F6236e325634AA8c1F692b5312bfF3A8558D0 | | AccountFactory | 0x870DB41df0905cc5a790f6582a3dA99A4A33F923 | | SwapModuleFactory | 0xC5a52E4bB718Dfe86938e5cB967362EdA1E62698 | | Consensus | 0x000000007e6b679B9196a1609e5Bc2405eDFd6Aa | | DepositQueue | 0x00000000B2d2373aAF1C370cFE4e1Ee8BDE7C546 | | SignatureDepositQueue | 0x000000000Af33501e5BDAF9B481Ad2712a024727 | | SyncDepositQueue | 0x000000001CC8c3E40856E956db870095EF6C98bd | | FeeManager | 0x00000000C18039E1F415fe07C33A316232238648 | | Oracle | 0x000000009adE4dAE1f868775A3f087945983f062 | | RedeemQueue | 0x0000000045d70ee8145135f08309fF5B1A63d43F | | SignatureRedeemQueue | 0x000000008D14Ef3658805765107d9F12776f4138 | | RiskManager | 0x00000000CC26BC741E75B181738Ac2B16156179b | | TokenizedShareManager | 0x00000000861e8B90B81f35C18cA14858Cc91d1Df | | BasicShareManager | 0x00000000e5F0cddA56447b2a29e2847A52c8725D | | BurnableTokenizedShareManager | 0x00000000C534B8680e3aa7165DeDc3Ab8781f602 | | Subvault | 0x00000000CA30010B8417f791250AE221FdaD5920 | | Verifier | 0x000000007e86a96e279662108cc19bA4c32EdE3C | | ERC20Verifier | 0x00000000ACD80376E999Af8c424e5e33BD224A08 | | MellowAccountV1 | 0x00000000860913f37fab81ce8ce4E5BD1f664482 | | SwapModule | 0x00000000c324E2d11EcCB03A061F69B5FE123645 | | Vault | 0x0000000070f44289ec5ea3E5972f058f75B29801 | | BitmaskVerifier | 0x0000000009E9368ad21fc19DCE1cFcf9Af6dE339 | | VaultConfigurator | 0x0000000005a67199ABE0f9C995EAB9DaDfA31Ccd | | BasicRedeemHook | 0x00000000176dD23550c3845746b2036E90DC5912 | | RedirectingDepositHook | 0x0000000024ABbd08686Abb2987831dEa88eF1180 | | OracleHelper | 0x000000007d2552AD746Af5c13f91B5e72f97c2B7 | | Contract | Address | | - | - | | Factory | 0x0000000072BAfCeAff1AD0237Ea58f06cfc4467F | | FactoryFactory | 0x00000000741292C88f9fF5050b07051C4f592EBf | | ConsensusFactory | 0xAfef40968b5304135677f0C89203948e1A145105 | | DepositQueueFactory | 0xF429ba2a8437E7de85078CF7481E8Ad52df7E58c | | RedeemQueueFactory | 0xe08dc488bD6756323F8bf478869529D03db627ef | | OracleFactory | 0x727c295b5D99b15280Ca8736b6F97ABA6aEd0E88 | | FeeManagerFactory | 0x52d56c20B0C8d403888880d0A1610e5ed17addA8 | | RiskManagerFactory | 0x9885215ef8DB25C87466E73018061e532784D716 | | ShareManagerFactory | 0xDA2a7aE07B6803feF9d95E47Ab83c8a5A09929F0 | | SubvaultFactory | 0xA64e324DFF04e3C0613ff0706867868C7b370a45 | | VaultFactory | 0xBBCD2aC50aF2EA12Cc9cb7B16dBDa85859BeB3da | | VerifierFactory | 0x9fBAF5AEB9F52bA57E1cC1D3050eac6d75Df8ae7 | | ERC20VerifierFactory | 0x711F6236e325634AA8c1F692b5312bfF3A8558D0 | | AccountFactory | 0x870DB41df0905cc5a790f6582a3dA99A4A33F923 | | SwapModuleFactory | 0xC5a52E4bB718Dfe86938e5cB967362EdA1E62698 | | Consensus | 0x000000007e6b679B9196a1609e5Bc2405eDFd6Aa | | DepositQueue | 0x00000000B2d2373aAF1C370cFE4e1Ee8BDE7C546 | | SignatureDepositQueue | 0x000000000Af33501e5BDAF9B481Ad2712a024727 | | SyncDepositQueue | 0x000000001CC8c3E40856E956db870095EF6C98bd | | FeeManager | 0x00000000C18039E1F415fe07C33A316232238648 | | Oracle | 0x000000009adE4dAE1f868775A3f087945983f062 | | RedeemQueue | 0x0000000045d70ee8145135f08309fF5B1A63d43F | | SignatureRedeemQueue | 0x000000008D14Ef3658805765107d9F12776f4138 | | RiskManager | 0x00000000CC26BC741E75B181738Ac2B16156179b | | TokenizedShareManager | 0x00000000861e8B90B81f35C18cA14858Cc91d1Df | | BasicShareManager | 0x00000000e5F0cddA56447b2a29e2847A52c8725D | | BurnableTokenizedShareManager | 0x00000000C534B8680e3aa7165DeDc3Ab8781f602 | | Subvault | 0x00000000CA30010B8417f791250AE221FdaD5920 | | Verifier | 0x000000007e86a96e279662108cc19bA4c32EdE3C | | ERC20Verifier | 0x00000000ACD80376E999Af8c424e5e33BD224A08 | | MellowAccountV1 | 0x00000000860913f37fab81ce8ce4E5BD1f664482 | | SwapModule | 0x0000000079d3FAb70077e5B920Ce067f11676351 | | Vault | 0x0000000070f44289ec5ea3E5972f058f75B29801 | | BitmaskVerifier | 0x0000000009E9368ad21fc19DCE1cFcf9Af6dE339 | | VaultConfigurator | 0x0000000005a67199ABE0f9C995EAB9DaDfA31Ccd | | BasicRedeemHook | 0x00000000176dD23550c3845746b2036E90DC5912 | | RedirectingDepositHook | 0x0000000024ABbd08686Abb2987831dEa88eF1180 | | OracleHelper | 0x000000007d2552AD746Af5c13f91B5e72f97c2B7 | | Smart Contract | Address | | - | - | | Factory | 0x0000000072BAfCeAff1AD0237Ea58f06cfc4467F | | FactoryFactory | 0x00000000741292C88f9fF5050b07051C4f592EBf | | ConsensusFactory | 0xAfef40968b5304135677f0C89203948e1A145105 | | DepositQueueFactory | 0xF429ba2a8437E7de85078CF7481E8Ad52df7E58c | | RedeemQueueFactory | 0xe08dc488bD6756323F8bf478869529D03db627ef | | OracleFactory | 0x727c295b5D99b15280Ca8736b6F97ABA6aEd0E88 | | FeeManagerFactory | 0x52d56c20B0C8d403888880d0A1610e5ed17addA8 | | RiskManagerFactory | 0x9885215ef8DB25C87466E73018061e532784D716 | | ShareManagerFactory | 0xDA2a7aE07B6803feF9d95E47Ab83c8a5A09929F0 | | SubvaultFactory | 0xA64e324DFF04e3C0613ff0706867868C7b370a45 | | VaultFactory | 0xBBCD2aC50aF2EA12Cc9cb7B16dBDa85859BeB3da | | VerifierFactory | 0x9fBAF5AEB9F52bA57E1cC1D3050eac6d75Df8ae7 | | ERC20VerifierFactory | 0x711F6236e325634AA8c1F692b5312bfF3A8558D0 | | AccountFactory | 0x870DB41df0905cc5a790f6582a3dA99A4A33F923 | | SwapModuleFactory | 0xC5a52E4bB718Dfe86938e5cB967362EdA1E62698 | | Consensus | 0x000000007e6b679B9196a1609e5Bc2405eDFd6Aa | | DepositQueue | 0x00000000B2d2373aAF1C370cFE4e1Ee8BDE7C546 | | SignatureDepositQueue | 0x000000000Af33501e5BDAF9B481Ad2712a024727 | | SyncDepositQueue | 0x000000001CC8c3E40856E956db870095EF6C98bd | | FeeManager | 0x00000000C18039E1F415fe07C33A316232238648 | | Oracle | 0x000000009adE4dAE1f868775A3f087945983f062 | | RedeemQueue | 0x0000000045d70ee8145135f08309fF5B1A63d43F | | SignatureRedeemQueue | 0x000000008D14Ef3658805765107d9F12776f4138 | | RiskManager | 0x00000000CC26BC741E75B181738Ac2B16156179b | | TokenizedShareManager | 0x00000000861e8B90B81f35C18cA14858Cc91d1Df | | BasicShareManager | 0x00000000e5F0cddA56447b2a29e2847A52c8725D | | BurnableTokenizedShareManager | 0x00000000C534B8680e3aa7165DeDc3Ab8781f602 | | Subvault | 0x00000000CA30010B8417f791250AE221FdaD5920 | | Verifier | 0x000000007e86a96e279662108cc19bA4c32EdE3C | | ERC20Verifier | 0x00000000ACD80376E999Af8c424e5e33BD224A08 | | MellowAccountV1 | 0x00000000860913f37fab81ce8ce4E5BD1f664482 | | SwapModule | 0x0000000042E248f84Df6BA1E768F878b1f2Bae9f | | Vault | 0x0000000070f44289ec5ea3E5972f058f75B29801 | | BitmaskVerifier | 0x0000000009E9368ad21fc19DCE1cFcf9Af6dE339 | | VaultConfigurator | 0x0000000005a67199ABE0f9C995EAB9DaDfA31Ccd | | BasicRedeemHook | 0x00000000176dD23550c3845746b2036E90DC5912 | | RedirectingDepositHook | 0x0000000024ABbd08686Abb2987831dEa88eF1180 | | OracleHelper | 0x000000007d2552AD746Af5c13f91B5e72f97c2B7 | | Smart Contract | Address | | - | - | | Factory | 0x0000000072BAfCeAff1AD0237Ea58f06cfc4467F | | FactoryFactory | 0x00000000741292C88f9fF5050b07051C4f592EBf | | ConsensusFactory | 0xAfef40968b5304135677f0C89203948e1A145105 | | DepositQueueFactory | 0xF429ba2a8437E7de85078CF7481E8Ad52df7E58c | | RedeemQueueFactory | 0xe08dc488bD6756323F8bf478869529D03db627ef | | OracleFactory | 0x727c295b5D99b15280Ca8736b6F97ABA6aEd0E88 | | FeeManagerFactory | 0x52d56c20B0C8d403888880d0A1610e5ed17addA8 | | RiskManagerFactory | 0x9885215ef8DB25C87466E73018061e532784D716 | | ShareManagerFactory | 0xDA2a7aE07B6803feF9d95E47Ab83c8a5A09929F0 | | SubvaultFactory | 0xA64e324DFF04e3C0613ff0706867868C7b370a45 | | VaultFactory | 0xBBCD2aC50aF2EA12Cc9cb7B16dBDa85859BeB3da | | VerifierFactory | 0x9fBAF5AEB9F52bA57E1cC1D3050eac6d75Df8ae7 | | ERC20VerifierFactory | 0x711F6236e325634AA8c1F692b5312bfF3A8558D0 | | AccountFactory | 0x870DB41df0905cc5a790f6582a3dA99A4A33F923 | | SwapModuleFactory | 0xC5a52E4bB718Dfe86938e5cB967362EdA1E62698 | | Consensus | 0x000000007e6b679B9196a1609e5Bc2405eDFd6Aa | | DepositQueue | 0x00000000B2d2373aAF1C370cFE4e1Ee8BDE7C546 | | SignatureDepositQueue | 0x000000000Af33501e5BDAF9B481Ad2712a024727 | | SyncDepositQueue | 0x000000001CC8c3E40856E956db870095EF6C98bd | | FeeManager | 0x00000000C18039E1F415fe07C33A316232238648 | | Oracle | 0x000000009adE4dAE1f868775A3f087945983f062 | | RedeemQueue | 0x0000000045d70ee8145135f08309fF5B1A63d43F | | SignatureRedeemQueue | 0x000000008D14Ef3658805765107d9F12776f4138 | | RiskManager | 0x00000000CC26BC741E75B181738Ac2B16156179b | | TokenizedShareManager | 0x00000000861e8B90B81f35C18cA14858Cc91d1Df | | BasicShareManager | 0x00000000e5F0cddA56447b2a29e2847A52c8725D | | BurnableTokenizedShareManager | 0x00000000C534B8680e3aa7165DeDc3Ab8781f602 | | Subvault | 0x00000000CA30010B8417f791250AE221FdaD5920 | | Verifier | 0x000000007e86a96e279662108cc19bA4c32EdE3C | | ERC20Verifier | 0x00000000ACD80376E999Af8c424e5e33BD224A08 | | MellowAccountV1 | 0x00000000860913f37fab81ce8ce4E5BD1f664482 | | SwapModule | 0x0000000022B540Fe06d7a9c32d81163971b583D6 | | Vault | 0x0000000070f44289ec5ea3E5972f058f75B29801 | | BitmaskVerifier | 0x0000000009E9368ad21fc19DCE1cFcf9Af6dE339 | | VaultConfigurator | 0x0000000005a67199ABE0f9C995EAB9DaDfA31Ccd | | BasicRedeemHook | 0x00000000176dD23550c3845746b2036E90DC5912 | | RedirectingDepositHook | 0x0000000024ABbd08686Abb2987831dEa88eF1180 | | OracleHelper | 0x000000007d2552AD746Af5c13f91B5e72f97c2B7 | | Smart Contract | Address | | - | - | | Factory Implementation | 0x000000092C4e111CBA592380b258d94B37038B63 | | Factory Factory | 0x00000003cEe0DbFb61dD598CD7978993A37f8F8C | | Consensus Factory | 0x4C496F31a4D46044E57214f282420b8b078edf56 | | Consensus Implementation | 0x00000008086A535Febd23fBd8C8F7d9D987930B7 | | DepositQueue Factory | 0x66B1a68F8CE628d508290d5C1d74Bc50416BDF90 | | DepositQueue Implementation | 0x0000000A151048f4f01996a9Cd35a982F5830251 | | SignatureDepositQueue Implementation | 0x00000003e7D5d1EDF85b03b974aAc374d0FCB8A1 | | SyncDepositQueue Implementation | 0x00000007d43702d556707a63132d42BcDf47E7dD | | FeeManager Factory | 0x71a4D9739A35B4e86118F3a45bae662Bcc9357FA | | FeeManager Implementation | 0x00000003bf6bEC83fA8ff147b04176B82F591497 | | Oracle Factory | 0xe4E2b5Db061A731D96b9267464c17Ba282326Ce7 | | Oracle Implementation | 0x00000001bdbaFbE0Fb55b7d74a6dB74D1DA6047E | | RedeemQueue Factory | 0x0a69B47c3E0bD7e5B1E1Db95d6C0b2914607e19f | | RedeemQueue Implementation | 0x00000002F8d3f0D03E9Ce461791F6A0a9d28D0f6 | | SignatureRedeemQueue Implementation | 0x000000047f8812704050cB86E549Fe8f28512A2D | | RiskManager Factory | 0x95ff5434A51f3E42fCeD2Cae36548d95e56bAb10 | | RiskManager Implementation | 0x0000000a0d139B4B7add54D70e2a4ED3c81C513C | | ShareManager Factory | 0x3755c140b90dC4E6b1A6361279B2C2eCc0358689 | | TokenizedShareManager Implementation | 0x0000000f2a485f26efd108144cCBFc46b18cB3e0 | | BasicShareManager Implementation | 0x0000000454d68af6Faf344e8acAa372f136749c5 | | Subvault Factory | 0x72244F91242244E62Af3417294B828E262EbdfE7 | | Subvault Implementation | 0x00000008c8A185371Ab8eB28bbdb875cd526B69C | | Verifier Factory | 0x40383d404e570D95fF68945d2a334fb2f5ecE0f6 | | Verifier Implementation | 0x00000007eEEbCA71f6b261061136BaFA666218A5 | | Vault Factory | 0xC7332ab052350Bbb9075f1160cc7073428981638 | | Vault Implementation | 0x00000002334dBFa3B92467eA9Eb970ec1e067377 | | BitmaskVerifier | 0x0000000f7DA5A9480262Ac3D654b1F4aA9F604B8 | | ERC20Verifier Factory | 0x7025132709b3B01D663D97e56eae37988471c75a | | ERC20Verifier | 0x000000010849B881DA846FFEb1078A433284F8D0 | | Account Factory | 0xCda7916AA830B4dAb8295FBa92953d5251f5FDFa | | MellowAccountV1 | 0x00000001f879d5dAE0066E714867014Ec265F4ab | | VaultConfigurator | 0x00000003986f4F63CdBAB0f5d78fff57495fee85 | | BasicRedeeHook | 0x000000033FDa28b7025Fb16D53F81c3C1F78d572 | | RedirectingDepositHook | 0x0000000Cbd64305e1668dB5F8a542c2c7EC61640 | | OracleHelper | 0x00000005dc87A3230E0F3195C5e9220DCFF1E182 | | OracleSubmitterFactory | 0x0000000dB76510D5B4D99df16160469bF782B227 | | DeployVaultFactoryRegistry | 0x00000008656A21E6f690d40BAc97736f62E54853 | | DeployVaultFactory | 0x00000005b5Dda102b4F9104fE8c537f816ac76D4 | | Component | Address | | - | - | | Factory Implementation | 0x00000004C12438f4593bb6C4047998020e60Fca8 | | Factory Factory | 0x0000000013eC5b779Ee3997A005088BCecDa551D | | Consensus Factory | 0xc52C25a06d2c7fbd349C2AD838544A1cF953b8eb | | Consensus Implementation | 0x0000000Ee53D9707851626b0E8485A8599bE95E7 | | DepositQueue Factory | 0x4176D4BD30a4AEF3F7ffcDD5Fe8997De807409a4 | | DepositQueue Implementation | 0x0000000eED98Aca517473d134Cc1a79c5a23b591 | | SignatureDepositQueue Implementation | 0x00000009A6488c99272A1ae297b7f364A348ba55 | | SyncDepositQueue Implementation | 0x0000000D38799D88008747b874822f55f0D35F6a | | FeeManager Factory | 0xcf41afeD6DE8A38F69235d414BB686f2847C19E0 | | FeeManager Implementation | 0x0000000852CF76C1c3dd8e74c817c442667f59D3 | | Oracle Factory | 0x03D4FfC7fB7bfec79EfF121201eB567A4d8E3AbA | | Oracle Implementation | 0x00000000fd75e0935c7101432F07E7D949a3709A | | RedeemQueue Factory | 0x3C2190540f6Cea0CD81f94e84F91f51644603238 | | RedeemQueue Implementation | 0x0000000A37A76557eAf5FF84D537C19aefb61c69 | | SignatureRedeemQueue Implementation | 0x00000009839691F13A8B2Bfb48a02338d5BB4282 | | RiskManager Factory | 0x6e72a6F11D0aCA5Cb21F94Dd78727Feb6b78224b | | RiskManager Implementation | 0x00000009BC5616c655EB3931d15553645F79e163 | | ShareManager Factory | 0xEd7d5E840c1567589f7354E278fcE3D549AC5a89 | | TokenizedShareManager Implementation | 0x0000000Ef763C2e0Fd309DaB48Bb4d5502ebe9F2 | | BasicShareManager Implementation | 0x00000000DAf16b90ee413672d0C7E51201A444a2 | | Subvault Factory | 0x2b49B9158640f576d50803868cB39C39c111b236 | | Subvault Implementation | 0x0000000A9671be5CA72833D21A5A048Bb59140A7 | | Verifier Factory | 0x318aec5cBf813eE085c7F5bc9285945d0cF97064 | | Verifier Implementation | 0x00000008d3117169514077a7d3e5e8B7cf76d4EA | | Vault Factory | 0xf1d7BE794A16767CA4485ec419984779c3221680 | | Vault Implementation | 0x0000000B84D4B6c47f975996CEdd67c475840CB0 | | BitmaskVerifier | 0x0000000022c92AC77562374F5e4617BF5fF7C2b5 | | ERC20Verifier Factory | 0x6D2416bc3A15EfF424577faD7914381DAA3172DE | | ERC20Verifier | 0x0000000dAb3d1f8724d96F8BECb864381a89C9C7 | | VaultConfigurator | 0x0000000D0e993ACc4ba4B8EaEC809866C068A3C2 | | BasicRedeeHook | 0x00000007A95AcE65df0d6F71660152c196f6330d | | RedirectingDepositHook | 0x0000000b0bFE39B38d95be646921D9E3756D27ee | | OracleHelper | 0x0000000e1a96d9abAb10F19b966F960efc8Ca989 | | Contract address | Address | | - | - | | Factory implementation | 0x00000008E7c244Fb6FA6Fc1fB5EC53Ec71c34386 | | Factory factory | 0x000000071a219faa713E719F2DfB458b10dbAED1 | | Consensus factory | 0xE4Db00dCc29966368E8aA966ac75B6FE5B4113D7 | | Consensus implementation | 0x000000083a1bE8Aa2Aa5fB244c84A6E410e6ce24 | | DepositQueue factory | 0x664B70AE0a01D9beF57c2Eb64664B0CFB055A461 | | DepositQueue implementation | 0x0000000518eC830D8C3da6056b34A0dfBF9e924d | | SignatureDepositQueue implementation | 0x00000006A03A937E4B316F02a5130e4FB0B22Dea | | SyncDepositQueue implementation | 0x0000000B813e85943D42c5187efAb487E12e1485 | | FeeManager factory | 0x4b2b12a33e260ef35e84687860785d263EfF5172 | | FeeManager implementation | 0x0000000df5Cff487723b8D8c58eD5C336d8a2317 | | Oracle factory | 0xbb7e4da67Fe8E66AB4AE8EAb2999e731DA364492 | | Oracle implementation | 0x0000000C705B7C7485F62Bc1DF7554fD6EB6C602 | | RedeemQueue factory | 0x7f2F6B155B41F2DD8A8Ea0FbA3b4EA1ceDc6260e | | RedeemQueue implementation | 0x00000000d06959064b28a46970497923f8834B16 | | SignatureRedeemQueue implementation | 0x000000004a3F4ff856e7cb47A0ae8aDe6d133cFB | | RiskManager factory | 0x902266fE38DD8Eee290987490bD57537c82007a1 | | RiskManager implementation | 0x0000000A7a10ea335C54E03220cfAe92310b2465 | | ShareManager factory | 0x6C3BB5478fD189DCf35Fb3a4b56015163392AA35 | | TokenizedShareManager implementation | 0x000000071F09E877c469749c093d09FB17896D6c | | BasicShareManager implementation | 0x00000008be96121073931e2b6Da8f5711a52097d | | Subvault factory | 0x3B62CaA341fA0535a479B394c2f6EA28ee8fA449 | | Subvault implementation | 0x0000000585bE8a415f9edCdC3C56472625BB2E02 | | Verifier factory | 0xbc1468D587DaEE3023E2b41Cc642643AF3221178 | | Verifier implementation | 0x000000097bD869258523A17D1e9836E71Ef8aB2A | | Vault factory | 0x5310AD84B0cd3Af376C751EAb81ceE414bD442d8 | | Vault implementation | 0x0000000B39b91D795b9975219E228bCb4D33A6A3 | | BitmaskVerifier | 0x0000000819BA998E0Dfe0DAfdd6B23dBf103314D | | ERC20Verifier factory | 0x0d634B6e35368b8954C53b38aDF72716a16667FA | | ERC20Verifier | 0x000000038Cd2281fe3C651A8B9C2380Ea15f2c87 | | VaultConfigurator | 0x00000000f731118c52AeA768c1ac22CEcA7e3b8D | | BasicRedeeHook | 0x0000000887657b16F0dc7EFbb2be9EA77cEDF16c | | RedirectingDepositHook | 0x0000000B77FC23f6F0f4c51238D6e1c76DefBFdb | | OracleHelper | 0x00000002FC616d31133ab9AD626E43a94674D5B6 | | OracleSubmitterFactory | 0x00000007AA9Bd15F538a2d1D68A2aCFE8D09BFd0 | | DeployVaultFactoryRegistry | 0x000000020893B447c2c13E4A8e5abCF5E7c09AeA | | DeployVaultFactory | 0x0000000bd67D6538614668EFe27aF3f17A3031dd | | Contract address | Address | | - | - | | Factory implementation | 0x0000000072BAfCeAff1AD0237Ea58f06cfc4467F | | Factory factory | 0x00000000741292C88f9fF5050b07051C4f592EBf | | Consensus factory | 0xAfef40968b5304135677f0C89203948e1A145105 | | Consensus implementation | 0x000000007e6b679B9196a1609e5Bc2405eDFd6Aa | | DepositQueue factory | 0xF429ba2a8437E7de85078CF7481E8Ad52df7E58c | | DepositQueue implementation | 0x00000000B2d2373aAF1C370cFE4e1Ee8BDE7C546 | | SignatureDepositQueue implementation | 0x000000000Af33501e5BDAF9B481Ad2712a024727 | | SyncDepositQueue implementation | 0x000000001CC8c3E40856E956db870095EF6C98bd | | FeeManager factory | 0x52d56c20B0C8d403888880d0A1610e5ed17addA8 | | FeeManager implementation | 0x00000000C18039E1F415fe07C33A316232238648 | | Oracle factory | 0x727c295b5D99b15280Ca8736b6F97ABA6aEd0E88 | | Oracle implementation | 0x000000009adE4dAE1f868775A3f087945983f062 | | RedeemQueue factory | 0xe08dc488bD6756323F8bf478869529D03db627ef | | RedeemQueue implementation | 0x0000000045d70ee8145135f08309fF5B1A63d43F | | SignatureRedeemQueue implementation | 0x000000008D14Ef3658805765107d9F12776f4138 | | RiskManager factory | 0x9885215ef8DB25C87466E73018061e532784D716 | | RiskManager implementation | 0x00000000CC26BC741E75B181738Ac2B16156179b | | ShareManager factory | 0xDA2a7aE07B6803feF9d95E47Ab83c8a5A09929F0 | | TokenizedShareManager implementation | 0x00000000861e8B90B81f35C18cA14858Cc91d1Df | | BasicShareManager implementation | 0x00000000e5F0cddA56447b2a29e2847A52c8725D | | BurnableTokenizedShareManager implementation | 0x00000000C534B8680e3aa7165DeDc3Ab8781f602 | | Subvault factory | 0xA64e324DFF04e3C0613ff0706867868C7b370a45 | | Subvault implementation | 0x00000000CA30010B8417f791250AE221FdaD5920 | | Verifier factory | 0x9fBAF5AEB9F52bA57E1cC1D3050eac6d75Df8ae7 | | Verifier implementation | 0x000000007e86a96e279662108cc19bA4c32EdE3C | | Vault factory | 0xBBCD2aC50aF2EA12Cc9cb7B16dBDa85859BeB3da | | Vault implementation | 0x0000000070f44289ec5ea3E5972f058f75B29801 | | BitmaskVerifier | 0x0000000009E9368ad21fc19DCE1cFcf9Af6dE339 | | ERC20Verifier factory | 0x711F6236e325634AA8c1F692b5312bfF3A8558D0 | | ERC20Verifier | 0x00000000ACD80376E999Af8c424e5e33BD224A08 | | Account factory | 0x870DB41df0905cc5a790f6582a3dA99A4A33F923 | | MellowAccountV1 implementation | 0x00000000860913f37fab81ce8ce4E5BD1f664482 | | SwapModule factory | 0xC5a52E4bB718Dfe86938e5cB967362EdA1E62698 | | SwapModule implementation | 0x00000000a00c7cf7e467bfD2aDeF6ce698D8D63a | | VaultConfigurator | 0x0000000005a67199ABE0f9C995EAB9DaDfA31Ccd | | BasicRedeemHook | 0x00000000176dD23550c3845746b2036E90DC5912 | | RedirectingDepositHook | 0x0000000024ABbd08686Abb2987831dEa88eF1180 | | OracleHelper | 0x000000007d2552AD746Af5c13f91B5e72f97c2B7 |
Instances Addresses for the Lido Earn vaults, earnETH, earnUSD, earnUSDc and earnUSDe, are on the [Lido Earn deployments](/lido-earn/deployments) page. Addresses for the USDG Yield Vault, rUSDG, are on the [USDG Yield Vault deployments](/usdg-vault/deployments) page. | Vault | [0x277C6A642564A91ff78b008022D65683cEE5CCC5](https://etherscan.io/address/0x277C6A642564A91ff78b008022D65683cEE5CCC5) | | - | - | | DepositQueue(ETH) | [0xE707321B887b9da133AC5fCc5eDB78Ab177a152D](https://etherscan.io/address/0xE707321B887b9da133AC5fCc5eDB78Ab177a152D) | | DepositQueue(WETH) | [0x2eA268f1018a4767bF5da42D531Ea9e943942A36](https://etherscan.io/address/0x2eA268f1018a4767bF5da42D531Ea9e943942A36) | | DepositQueue(WSTETH) | [0x614cb9E9D13712781DfD15aDC9F3DAde60E4eFAb](https://etherscan.io/address/0x614cb9E9D13712781DfD15aDC9F3DAde60E4eFAb) | | SyncDepositQueue(ETH) | [0x3b261535055c9faf248fe5b23a743184b6d4beed](https://etherscan.io/address/0x3b261535055c9faf248fe5b23a743184b6d4beed) | | SyncDepositQueue(WETH) | [0x8a89f2c30673520a45839353c32c30bbe59fe616](https://etherscan.io/address/0x8a89f2c30673520a45839353c32c30bbe59fe616) | | SyncDepositQueue(WSTETH) | [0x6c9abc05cdb7d1c339dcc359f40e8bde65c1bd99](https://etherscan.io/address/0x6c9abc05cdb7d1c339dcc359f40e8bde65c1bd99) | | SyncDepositQueue(DVV) | [0xbcdbf4d3b3b3345f8348c6aa557949deaa6bc57b](https://etherscan.io/address/0xbcdbf4d3b3b3345f8348c6aa557949deaa6bc57b) | | RedeemQueue(WSTETH) | [0x1ae8C006b5C97707aa074AaeD42BecAD2CF80Da2](https://etherscan.io/address/0x1ae8C006b5C97707aa074AaeD42BecAD2CF80Da2) | | SyncRedeemQueue(WSTETH) | [0x6eCd9fe8cA919CB6DBCdD07D47Ef8548E0c5C481](https://etherscan.io/address/0x6eCd9fe8cA919CB6DBCdD07D47Ef8548E0c5C481) | | Oracle | [0x8a78e6b7E15C4Ae3aeAeE3bf0DE4F2de4078c1cD](https://etherscan.io/address/0x8a78e6b7E15C4Ae3aeAeE3bf0DE4F2de4078c1cD) | | ShareManager | [0xcd3c0F51798D1daA92Fb192E57844Ae6cEE8a6c7](https://etherscan.io/address/0xcd3c0F51798D1daA92Fb192E57844Ae6cEE8a6c7) | | FeeManager | [0x24FD64EB4766d91FD79a4D5e8086b2460dEBcaE7](https://etherscan.io/address/0x24FD64EB4766d91FD79a4D5e8086b2460dEBcaE7) | | RiskManager | [0x4f6bc03537C6F74E250f57a9a7238087caBF1c6D](https://etherscan.io/address/0x4f6bc03537C6F74E250f57a9a7238087caBF1c6D) | | Subvault0 | [0x90c983DC732e65DB6177638f0125914787b8Cb78](https://etherscan.io/address/0x90c983DC732e65DB6177638f0125914787b8Cb78) | | Verifier0 | [0xF4eA276361348b301Ba2296dB909a7c973A15451](https://etherscan.io/address/0xF4eA276361348b301Ba2296dB909a7c973A15451) | | Subvault1 | [0x893aa69FBAA1ee81B536f0FbE3A3453e86290080](https://etherscan.io/address/0x893aa69FBAA1ee81B536f0FbE3A3453e86290080) | | Verifier1 | [0x02e1C91C4D82af454D892FBE2c5De2c4504b2675](https://etherscan.io/address/0x02e1C91C4D82af454D892FBE2c5De2c4504b2675) | | Subvault2 | [0x181cB55f872450D16aE858D532B4e35e50eaA76D](https://etherscan.io/address/0x181cB55f872450D16aE858D532B4e35e50eaA76D) | | Verifier2 | [0x1616d39a201D246cbD1B3B145234638f7719b53A](https://etherscan.io/address/0x1616d39a201D246cbD1B3B145234638f7719b53A) | | Subvault3 | [0x9938A09FeA37bA681A1Bd53D33ddDE2dEBEc1dA0](https://etherscan.io/address/0x9938A09FeA37bA681A1Bd53D33ddDE2dEBEc1dA0) | | Verifier3 | [0xd662dF7C0FAF0Fe6446638651b05C287806AD1AE](https://etherscan.io/address/0xd662dF7C0FAF0Fe6446638651b05C287806AD1AE) | | Subvault 4 | [0x3883d8CdCdda03784908cFa2F34ED2cF1604e4d7](https://etherscan.io/address/0x3883d8CdCdda03784908cFa2F34ED2cF1604e4d7) | | Verifier 4 | [0x58C4B6B0d6CFf1d684E4B8Ee899550F4B68A1031](https://etherscan.io/address/0x58C4B6B0d6CFf1d684E4B8Ee899550F4B68A1031) | | Subvault 5 | [0xECf3BDE9f50F71edE67E05050123b64b519DF55C](https://etherscan.io/address/0xECf3BDE9f50F71edE67E05050123b64b519DF55C) | | Verifier 5 | [0xb0d19Eef486b4807Ab1fe20AB4cfaCB074592Ea5](https://etherscan.io/address/0xb0d19Eef486b4807Ab1fe20AB4cfaCB074592Ea5) | | Subvault 6 | [0xCDfA7EfE670869c6b6be4375654E0b206eF49c89](https://etherscan.io/address/0xCDfA7EfE670869c6b6be4375654E0b206eF49c89) | | Verifier 6 | [0x9C5D826e1BcdF67f0596725CbB931dC02132D88d](https://etherscan.io/address/0x9C5D826e1BcdF67f0596725CbB931dC02132D88d) | | Subvault 7 | [0x888d2A3E9B600F360a3386c9D2fEdFa658E7fA29](https://etherscan.io/address/0x888d2A3E9B600F360a3386c9D2fEdFa658E7fA29) | | Verifier 7 | [0xDF96d59d3688C56ca29aED045FE67C84bbc38461](https://etherscan.io/address/0xDF96d59d3688C56ca29aED045FE67C84bbc38461) | | Vault Arbitrum | [0x40330720039352B309c70A5028322D1481F496d1](https://arbiscan.io/address/0x40330720039352B309c70A5028322D1481F496d1) | | Subvault Arbitrum | [0x222fa99C485a088564eb43fAA50Bc10b2497CDB2](https://arbiscan.io/address/0x222fa99C485a088564eb43fAA50Bc10b2497CDB2) | | Verifier Arbitrum | [0x022a33293aeD00E59E93D354D3810249Fa33d7D4](https://arbiscan.io/address/0x022a33293aeD00E59E93D354D3810249Fa33d7D4) | | TimelockController Arbitrum | [0xda1674c1135eA98A311a3b4aa11865266ab52A7C](https://arbiscan.io/address/0xda1674c1135eA98A311a3b4aa11865266ab52A7C) | | Vault Plasma | [0x841e213864046111E43d237703d71FaBe91Ef9e0](https://plasmascan.to/address/0x841e213864046111E43d237703d71FaBe91Ef9e0) | | Subvault 0 Plasma | [0xbbF9400C09B0F649F3156989F1CCb9c016f943bb](https://plasmascan.to/address/0xbbF9400C09B0F649F3156989F1CCb9c016f943bb) | | Verifier 0 Plasma | [0xC724D3bA28e24e243f653c626F8BEA44113b3a0b](https://plasmascan.to/address/0xC724D3bA28e24e243f653c626F8BEA44113b3a0b) | | Subvault 1 Plasma | [0x7696731721dddeC1502F35C52d9c83a768227daE](https://plasmascan.to/address/0x7696731721dddeC1502F35C52d9c83a768227daE) | | Verifier 1 Plasma | [0xad1b3ea06027df987d1320e1f4d0f1b58230250c](https://plasmascan.to/address/0xad1b3ea06027df987d1320e1f4d0f1b58230250c) | | TimelockController Plasma | [0x3169036c3F79c03C14a7496DD2016f5B059e17D8](https://plasmascan.to/address/0x3169036c3F79c03C14a7496DD2016f5B059e17D8) | | SwapModule 0 | [0x25091725982e83f6aFDf6A17705FeECA5866B864](https://etherscan.io/address/0x25091725982e83f6aFDf6A17705FeECA5866B864) | | SwapModule 1 | [0x7a57d62b1217Ce5685E26C333741e61b99233E65](https://etherscan.io/address/0x7a57d62b1217Ce5685E26C333741e61b99233E65) | | SwapModule 3 | [0xc95b806aC073Df930014ac476d26c8ad918f14e0](https://etherscan.io/address/0xc95b806aC073Df930014ac476d26c8ad918f14e0) | | SwapModule 4 | [0x35D482D0BBbb1c2F25d9b12F234883f3224f3198](https://etherscan.io/address/0x35D482D0BBbb1c2F25d9b12F234883f3224f3198) | | SwapModule 5 | [0xE3B023d3FF076E35448c936dA5e8F6adA6130ca4](https://etherscan.io/address/0xE3B023d3FF076E35448c936dA5e8F6adA6130ca4) | | SwapModule 6 | [0x2a166aE48F9F1FC27685582a61250011fd5363D8](https://etherscan.io/address/0x2a166aE48F9F1FC27685582a61250011fd5363D8) | | SwapModule 7 | [0xb6451d4EaEC79fD22b69086a5B760a166bd28C52](https://etherscan.io/address/0xb6451d4EaEC79fD22b69086a5B760a166bd28C52) | | Plasma SwapModule 0 | [0x0A974551c45cFb9e002D06B2AB82ae20e800D000](https://plasmascan.to/address/0x0A974551c45cFb9e002D06B2AB82ae20e800D000) | | Plasma SwapModule 1 | [0xa330Cc14988321160fd26D9F202cBd845328B6e2](https://plasmascan.to/address/0xa330Cc14988321160fd26D9F202cBd845328B6e2) | | Timelockcontroller | [0x8D8b65727729Fb484CB6dc1452D61608a5758596](https://etherscan.io/address/0x8D8b65727729Fb484CB6dc1452D61608a5758596) | | Lazyadmin | [0xAbE20D266Ae54b9Ae30492dEa6B6407bF18fEeb5](https://etherscan.io/address/0xAbE20D266Ae54b9Ae30492dEa6B6407bF18fEeb5) | | Activeadmin | [0xeb1CaFBcC8923eCbc243ff251C385C201A6c734a](https://etherscan.io/address/0xeb1CaFBcC8923eCbc243ff251C385C201A6c734a) | | Oracleupdater | [0xd27fFB15Dd00D5E52aC2BFE6d5AFD36caE850081](https://etherscan.io/address/0xd27fFB15Dd00D5E52aC2BFE6d5AFD36caE850081) | | OracleSubmitter | [0x00000000df0088bd598df1e4ae57943dc481907a](https://etherscan.io/address/0x00000000df0088bd598df1e4ae57943dc481907a) | | Curator | [0x5Dbf9287787A5825beCb0321A276C9c92d570a75](https://etherscan.io/address/0x5Dbf9287787A5825beCb0321A276C9c92d570a75) | | Mellowsrtethtreasury | [0xb1E5a8F26C43d019f2883378548a350ecdD1423B](https://etherscan.io/address/0xb1E5a8F26C43d019f2883378548a350ecdD1423B) | | Emergencypauser | [0xa6278B726d4AA09D14f9E820D7785FAd82E7196F](https://etherscan.io/address/0xa6278B726d4AA09D14f9E820D7785FAd82E7196F) | | Vault | [0xDbC81B33A23375A90c8Ba4039d5738CB6f56fE8d](https://etherscan.io/address/0xDbC81B33A23375A90c8Ba4039d5738CB6f56fE8d) | | - | - | | DepositQueue (ETH) | [0xfA428FDBC73AD52C12724096e73e213FCd307141](https://etherscan.io/address/0xfA428FDBC73AD52C12724096e73e213FCd307141) | | DepositQueue (WETH) | [0x729f0fE9c1BE8e4a814695277d7B56AD2427487e](https://etherscan.io/address/0x729f0fE9c1BE8e4a814695277d7B56AD2427487e) | | DepositQueue (WSTETH) | [0x34b46C8e622730C7115bB71c720F939274F73bEc](https://etherscan.io/address/0x34b46C8e622730C7115bB71c720F939274F73bEc) | | RedeemQueue (ETH) | [0xa1CE84069E5AC305075B0d54EBc12389BF674d18](https://etherscan.io/address/0xa1CE84069E5AC305075B0d54EBc12389BF674d18) | | RedeemQueue (WETH) | [0x5490729bBB5821C56F8598b856ae353435b9E993](https://etherscan.io/address/0x5490729bBB5821C56F8598b856ae353435b9E993) | | RedeemQueue (WSTETH) | [0x52135C49e6d734866AB37e127de03B479559A018](https://etherscan.io/address/0x52135C49e6d734866AB37e127de03B479559A018) | | Oracle | [0x7AD1D268Ab39612345CDACbC53bD7FFe806a3919](https://etherscan.io/address/0x7AD1D268Ab39612345CDACbC53bD7FFe806a3919) | | ShareManager | [0x4076D217fAA2813165235b4f0D9C03B67bfF9355](https://etherscan.io/address/0x4076D217fAA2813165235b4f0D9C03B67bfF9355) | | FeeManager | [0x3fEA00B3f535fCd2A77840563b48EDFee71B0E85](https://etherscan.io/address/0x3fEA00B3f535fCd2A77840563b48EDFee71B0E85) | | RiskManager | [0x22D047637D6aCD0B5525b5bD1449AE9f9575FEc5](https://etherscan.io/address/0x22D047637D6aCD0B5525b5bD1449AE9f9575FEc5) | | Subvault 0 | [0x2e2b73616e67c1C3660111baE981483a126e8F7a](https://etherscan.io/address/0x2e2b73616e67c1C3660111baE981483a126e8F7a) | | Verifier 0 | [0x6AF924f6450b2B5FdE62160bdD3eD076Ad92BCa1](https://etherscan.io/address/0x6AF924f6450b2B5FdE62160bdD3eD076Ad92BCa1) | | Subvault 1 | [0x8dCEEfBD6E3A9D1Af10C3e3c757Cb32FeB8b3e2a](https://etherscan.io/address/0x8dCEEfBD6E3A9D1Af10C3e3c757Cb32FeB8b3e2a) | | Verifier 1 | [0x2c862fD2D1802a4B7186f80cA36F6eA6bECf8613](https://etherscan.io/address/0x2c862fD2D1802a4B7186f80cA36F6eA6bECf8613) | | Timelock controller | [0xd2Be05F489202A1ADb20d200c89624AcbF403ae7](https://etherscan.io/address/0xd2Be05F489202A1ADb20d200c89624AcbF403ae7) | | ProxyAdmin | [0x55d9ecEB5733F72A48C544e20D49859eC92Fba5F](https://etherscan.io/address/0x55d9ecEB5733F72A48C544e20D49859eC92Fba5F) | | LazyAdmin | [0x8907D6089fC71AA6a9a7bb9EC5b1170e92489ebf](https://etherscan.io/address/0x8907D6089fC71AA6a9a7bb9EC5b1170e92489ebf) | | ActiveAdmin | [0xD95cb50F204B8B84606751F262b407C08528c85 ](https://etherscan.io/address/0xD95cb50F204B8B84606751F262b407C08528c85) | | OracleUpdater | [0xe5Bc509b277f83F2bF771D0dcB16949D4e175f09 ](https://etherscan.io/address/0xe5Bc509b277f83F2bF771D0dcB16949D4e175f09) | | Curator | [0xcca5BafEa783B0Ed8D11FD6D9F97c155332A16b8](https://etherscan.io/address/0xcca5BafEa783B0Ed8D11FD6D9F97c155332A16b8) | | EmergencyPauser | [0x8907D6089fC71AA6a9a7bb9EC5b1170e92489ebf](https://etherscan.io/address/0x8907D6089fC71AA6a9a7bb9EC5b1170e92489ebf) | | tqETH Treasury | [0x8907D6089fC71AA6a9a7bb9EC5b1170e92489ebf](https://etherscan.io/address/0x8907D6089fC71AA6a9a7bb9EC5b1170e92489ebf) | | Contract | Address | | - | - | | Vault | [0x7207595E4c18a9A829B9dc868F11F3ADd8FCF626](https://etherscan.io/address/0x7207595E4c18a9A829B9dc868F11F3ADd8FCF626) | | DepositQueue (USDC) | [0xA9e5120dD134E42BE7EBA828C5eA857dc9c75D83](https://etherscan.io/address/0xA9e5120dD134E42BE7EBA828C5eA857dc9c75D83) | | DepositQueue (USDT) | [0xeB9257C278ab03F30Fe889D98074c0eBDFB6779a](https://etherscan.io/address/0xeB9257C278ab03F30Fe889D98074c0eBDFB6779a) | | DepositQueue (MUSD) | [0x5c5C00D7bD4b6AC55466284c2963b3a0D3568Ad3](https://etherscan.io/address/0x5c5C00D7bD4b6AC55466284c2963b3a0D3568Ad3) | | RedeemQueue (USDC) | [0xE1fC4025c4C62d0e3876296E1cE86EBB1b6dceC2](https://etherscan.io/address/0xE1fC4025c4C62d0e3876296E1cE86EBB1b6dceC2) | | RedeemQueue (USDT) | [0x57543a4F671829B3F11241C9EC1432d258810Da1](https://etherscan.io/address/0x57543a4F671829B3F11241C9EC1432d258810Da1) | | RedeemQueue (MUSD) | [0x7545F100D91a335187ADB57a0F02356408D2DCba](https://etherscan.io/address/0x7545F100D91a335187ADB57a0F02356408D2DCba) | | Oracle | [0xccB10707cc3105178CBef8ee5b7DC84D5d1b277F](https://etherscan.io/address/0xccB10707cc3105178CBef8ee5b7DC84D5d1b277F) | | ShareManager | [0xe4741d6901C77Da80FAEeD7E2fE10c8b348Bcc84](https://etherscan.io/address/0xe4741d6901C77Da80FAEeD7E2fE10c8b348Bcc84) | | FeeManager | [0x7D3074904e3ccB61671fd3716dD4acE565B18320](https://etherscan.io/address/0x7D3074904e3ccB61671fd3716dD4acE565B18320) | | RiskManager | [0xc73EFdb68b839932933bf9eB4dB908fcA33677B3](https://etherscan.io/address/0xc73EFdb68b839932933bf9eB4dB908fcA33677B3) | | Timelock controller | [0xb3Ffd1a8C0cBA695DfA32603851BF99dDB6F7354](https://etherscan.io/address/0xb3Ffd1a8C0cBA695DfA32603851BF99dDB6F7354) | | Contract | Address | | - | - | | Vault | [0x07AFFA6754458f88db83A72859948d9b794E131b](https://explorer.mezo.org/address/0x07AFFA6754458f88db83A72859948d9b794E131b) | | Subvault #0 | [0x6F05747CdFe61b998f928CE509547CB630A981a1](https://explorer.mezo.org/address/0x6F05747CdFe61b998f928CE509547CB630A981a1) | | Verifier | [0x955fbdD0D65d719A2F815ED55401997c863E12b4](https://explorer.mezo.org/address/0x955fbdD0D65d719A2F815ED55401997c863E12b4) | | Oracle | [0xe3FDB2436c0F6B16F6b6ed903B90bE8BF0D0Cf85](https://explorer.mezo.org/address/0xe3FDB2436c0F6B16F6b6ed903B90bE8BF0D0Cf85) | | ShareManager | [0xc5834dc9EDe2b1d6aE7e52150e95Ccfd12df0999](https://explorer.mezo.org/address/0xc5834dc9EDe2b1d6aE7e52150e95Ccfd12df0999) | | FeeManager | [0x8B01DC1D71bB1270622139565C1Ead0FaD875F29](https://explorer.mezo.org/address/0x8B01DC1D71bB1270622139565C1Ead0FaD875F29) | | RiskManager | [0x3ab2603982F120F27EE8C40d3299B8446a7bDc88](https://explorer.mezo.org/address/0x3ab2603982F120F27EE8C40d3299B8446a7bDc88) | | TimelockController | [0x8FBc3241b37C8Ab39Bd805D3233F63643c879C6C](https://explorer.mezo.org/address/0x8FBc3241b37C8Ab39Bd805D3233F63643c879C6C) | | Contract | Address | | - | - | | Vault | [0x63a76a4a94cAB1DD49fcf0d7E3FC53a78AC8Ec5C](https://etherscan.io/address/0x63a76a4a94cAB1DD49fcf0d7E3FC53a78AC8Ec5C) | | Subvault #0 | [0x699d09f862e0d3D093b522562c42e9eFBcEE207f](https://etherscan.io/address/0x699d09f862e0d3D093b522562c42e9eFBcEE207f) | | Verifier | [0xECD83240Bdfbb51F17C6c6D0F5805959C3438AC7](https://etherscan.io/address/0xECD83240Bdfbb51F17C6c6D0F5805959C3438AC7) | | Oracle | [0xf40bC75d53eA7a015f2452F557e3353D20c2f419](https://etherscan.io/address/0xf40bC75d53eA7a015f2452F557e3353D20c2f419) | | ShareManager | [0x171b8E43bB751A558b2b1f3C814d3c96D36cCf2B](https://etherscan.io/address/0x171b8E43bB751A558b2b1f3C814d3c96D36cCf2B) | | FeeManager | [0xa93DD91a799Ca5015fa182514959A2bB4772B4e6](https://etherscan.io/address/0xa93DD91a799Ca5015fa182514959A2bB4772B4e6) | | RiskManager | [0x73ea494Ea0eca57b686169203a85F64505457a2e](https://etherscan.io/address/0x73ea494Ea0eca57b686169203a85F64505457a2e) | | TimelockController | [0x3279Ef5414a97dDFa6DA89308AeD54e6a0D75Df2](https://etherscan.io/address/0x3279Ef5414a97dDFa6DA89308AeD54e6a0D75Df2) | | DepositQueue (cbBTC) | [0x0ee16cF1a0CA0D1cA070a6FEC1595888B22Dd02f](https://etherscan.io/address/0x0ee16cF1a0CA0D1cA070a6FEC1595888B22Dd02f) | | Vault | [0x06ED1E2167AA7FBf2476c5A2D220Bf702559Dcf8](https://explorer.mezo.org/address/0x06ED1E2167AA7FBf2476c5A2D220Bf702559Dcf8) | | - | - | | Subvault #0 | [0x694066A256DcbAC4301843d17C863A52380B6316](https://explorer.mezo.org/address/0x694066A256DcbAC4301843d17C863A52380B6316) | | Verifier | [0xbbEd9cF82B7531aa542e32Be4E00c477aa8760E2](https://explorer.mezo.org/address/0xbbEd9cF82B7531aa542e32Be4E00c477aa8760E2) | | Oracle | [0x06886f9F737999c592e306Aa0090F2d8C2a01C13](https://explorer.mezo.org/address/0x06886f9F737999c592e306Aa0090F2d8C2a01C13) | | ShareManager | [0x8FB0EB4BB6CA5cf3883E83734BD5bD77a87CC20E](https://explorer.mezo.org/address/0x8FB0EB4BB6CA5cf3883E83734BD5bD77a87CC20E) | | FeeManager | [0xb69FF5f7528391E18D91f448f79498575df66146](https://explorer.mezo.org/address/0xb69FF5f7528391E18D91f448f79498575df66146) | | RiskManager | [0x8982d843F25b357720C0dbbEfd03dbE9824d0174](https://explorer.mezo.org/address/0x8982d843F25b357720C0dbbEfd03dbE9824d0174) | | TimelockController | [0xE7A5363B869D4794544E4fF9Fd9ee1A6F038b517](https://explorer.mezo.org/address/0xE7A5363B869D4794544E4fF9Fd9ee1A6F038b517) | | DepositQueue (mcbBTC) | [0x28413A9D4b25BFff5Cf3322F1013aB3849bf89Ad](https://explorer.mezo.org/address/0x28413A9D4b25BFff5Cf3322F1013aB3849bf89Ad) | | Contract | Address | | - | - | | Vault | [0xa8A3De0c5594A09d0cD4C8abc4e3AaB9BaE03F36](https://etherscan.io/address/0xa8A3De0c5594A09d0cD4C8abc4e3AaB9BaE03F36) | | Subvault #0 | [0xC22642ad548183aFbe389dc667d698C60f3D9a22](https://etherscan.io/address/0xC22642ad548183aFbe389dc667d698C60f3D9a22) | | Verifier | [0x6e9B4381900f19054916d5DBf238B99ED017F49d](https://etherscan.io/address/0x6e9B4381900f19054916d5DBf238B99ED017F49d) | | Oracle | [0x1786e893dB43aBe03517Bd99985aEEdC3EE4848F](https://etherscan.io/address/0x1786e893dB43aBe03517Bd99985aEEdC3EE4848F) | | ShareManager | [0x43f084bdBC99409c637319dD7c544D565165A162](https://etherscan.io/address/0x43f084bdBC99409c637319dD7c544D565165A162) | | FeeManager | [0x95B46F24cf5425b117Ac24E498852ADbD705Feb7](https://etherscan.io/address/0x95B46F24cf5425b117Ac24E498852ADbD705Feb7) | | RiskManager | [0x8626d581C16FE972beC234BC041cEce47CAb9c55](https://etherscan.io/address/0x8626d581C16FE972beC234BC041cEce47CAb9c55) | | TimelockController | [0x972Ae54BF6950FDe7539a803CC5cD71B0F2F0CB2](https://etherscan.io/address/0x972Ae54BF6950FDe7539a803CC5cD71B0F2F0CB2) | | DepositQueue (tBTC) | [0x0823b68c7e00B327f97b1Bf48eD44ef9CD11fb74](https://etherscan.io/address/0x0823b68c7e00B327f97b1Bf48eD44ef9CD11fb74) | | DepositQueue (WBTC) | [0xb10feC1df8FCFF2bf06fbd1AeAc34B87EA4e9AC2](https://etherscan.io/address/0xb10feC1df8FCFF2bf06fbd1AeAc34B87EA4e9AC2) | | Contract | Address | | - | - | | Vault | [0x807D4778abA870e4222904f5b528F68B350cE0E0](https://explorer.mezo.org/address/0x807D4778abA870e4222904f5b528F68B350cE0E0) | | Subvault #0 | [0x26310E42d8DE572a27Acc6C8D77946968baC5E79](https://explorer.mezo.org/address/0x26310E42d8DE572a27Acc6C8D77946968baC5E79) | | Verifier | [0xe179A0B3e741ED19DE305dd1aA401268c9b34D24](https://explorer.mezo.org/address/0xe179A0B3e741ED19DE305dd1aA401268c9b34D24) | | Oracle | [0xd65b33625Bb2214ee18545207Ad16CB3342A748a](https://explorer.mezo.org/address/0xd65b33625Bb2214ee18545207Ad16CB3342A748a) | | ShareManager | [0xE2232789D4cF5bb1ffaDA1a105Cc59B18d639318](https://explorer.mezo.org/address/0xE2232789D4cF5bb1ffaDA1a105Cc59B18d639318) | | FeeManager | [0xC047F4C95Dd253A8369C49Ef963236611730Fd00](https://explorer.mezo.org/address/0xC047F4C95Dd253A8369C49Ef963236611730Fd00) | | RiskManager | [0xcA3eD47184bD56B3eD4Ae9eACdc3D20c05FbCb0A](https://explorer.mezo.org/address/0xcA3eD47184bD56B3eD4Ae9eACdc3D20c05FbCb0A) | | TimelockController | [0x0be428356914e587cEB97DBddFBC894d7a594656](https://explorer.mezo.org/address/0x0be428356914e587cEB97DBddFBC894d7a594656) | | DepositQueue (BTC) | [0x88e2641C6d6dACdc451F5d91787d7a58cbc6B6B1](https://explorer.mezo.org/address/0x88e2641C6d6dACdc451F5d91787d7a58cbc6B6B1) | Smart contracts of earnUSD
| Component | Address | | - | - | | Vault | 0x014e6DA8F283C4aF65B2AA0f201438680A004452 | | DepositQueue (USDC) | 0xC75E7E73B25fEa8bB23EB55CC48BA55067b5be76 | | SyncDepositQueue (USDC) | 0xf6AFAf6afcAe116dD37A779D50fE6c5fa6f8C8f5 | | AsyncDepositQueue (USDe) | 0xeec37568b01e0c4d5028501a49e024b475e2d7ca | | RedeemQueue (USDC) | 0x9e36A74FE278906a76e7615263e46a83fC40c47F | | DepositQueue (USDT) | 0xEeC5041c47Cba1e31321AC6941Bf09Ad60645B73 | | SyncDepositQueue (USDT) | 0x534d0bEb82C47cf703BFb9E959297658b65Ec8E9 | | AsyncRedeemQueue (USDT) | 0x95092A7a86715246Be6395b8D514B3d60A270Cd3 | | Oracle | 0x827044735c9708a2cf850e7Ea37EBa43bc786028 | | ShareManager | 0x4Ce1ac8F43E0E5BD7A346A98aF777bF8fbeA1981 | | FeeManager | 0x72fa23f40e08eB9E45953233b2Dd9665E347e8Dc | | RiskManager | 0x7b1e06C46d4510277FC37a37bBeF65F3794fdDE4 | | Subvault 0 | 0x77B9441d5Cb89fca435190A9B6D108ad4B00ccFd | | Verifier 0 | 0xB65A8E0937c77a76C3f4F86A1110f81A299CB481 | | Timelock Controller | 0xdA6Da82DFF8cD29D828e4775Cc003f504A968845 | | OracleSubmitter | 0xB105DaEeFEb1390ce49172c99E3e12C607367156 |
| Contract Address | Address | | - | - | | Deployer | 0x4d551d74e851Bd93Ce44D5F588Ba14623249CDda | | Vault | 0x97Bb1d1b9FaA1091406A005F0B4a1658e5b542Eb | | DepositQueue (RBTC) | 0x0f04AE0D8Ebf40b2D3DDB51d9dba279Fe9C7F220 | | RedeemQueue (RBTC) | 0x2177f2b5A9C669BfBc1e68A07DBa52908Cd01efC | | Oracle | 0x4652207b2df6F3Bcee350702E66A2C17a96dBdDB | | OracleSubmitter | 0xf00790d7F8Db7AD26FB3e84fe1Cc7f084D01484c | | ShareManager | 0x7011cb71431abFB4ACcDfCF1Ee5291b898487229 | | FeeManager | 0xAFB0CF92B5fC25daA04db870064CFA12ECAe48C5 | | RiskManager | 0x3075FA2e4305172EAB8c26106057ADE96f3F4E37 | | Subvault 0 | 0x4eA6234e9bce5F5957427eC612451A347CEc03db | | Verifier 0 | 0x02CAf23746Ff8076e4b4AFa21fa4E2790d297dd5 | | Timelock controller | 0x3FF89E7Cf116462950165a31cEd1D5c8A946FcB0 |
# Overview Source: https://docs.mellow.finance/core-vaults/overview What Core Vaults are, why they exist, how they encode strategy constraints onchain, and the curated model separating depositors from curators. Core Vaults are Mellow's current vault architecture for deploying curated onchain structured products. They exist to address a specific class of problems that emerge once vaults move beyond simple asset routing: the need for explicit risk boundaries, predictable execution surfaces, and stable interfaces that remain valid as strategies evolve. Core Vaults formalize these requirements at the vault level. Core Vaults encode strategy constraints directly into the vault configuration. This includes which integrations can be used, which actions are permitted, and how capital can move between execution paths. Enforcement happens onchain, making strategy behavior inspectable and resistant to off-policy execution. Core Vaults are designed to be extensible without requiring protocol-specific adapters for every new integration. Instead of hard-coding execution paths per protocol, Core Vaults expose a flexible execution surface that allows curators to route capital to new DeFi venues within the bounds of existing vault constraints. This significantly reduces integration overhead and shortens time-to-deployment, enabling strategies to incorporate new protocols or shift execution paths as market conditions change. As a result, Core Vaults can adapt strategy behavior without requiring redeployment of the vault itself, while still preserving onchain enforcement of permissions and risk limits. Core Vaults operate under a curated model. Depositors supply capital to the vault, while curators operate strategy logic within predefined guardrails. Curators cannot arbitrarily change strategy behavior. All actions are bounded by the vault's permissions, limits, and execution rules. This separation of roles allows strategies to remain actively managed while keeping risk parameters explicit and enforceable. # Deployments Source: https://docs.mellow.finance/deployments Products running on Mellow Core Vaults: Lido EarnETH, Lido EarnUSD and the USDG Yield Vault, with Mellow's role, other parties, deposit assets and chains. Products running on Mellow Core Vaults. Each entry links to its documentation. Contract addresses are on each product's deployments page and on [Core Vault deployments](/core-vaults/core-deployments). Current figures for each vault are shown in the [Mellow app](https://app.mellow.finance/vaults). ## Live | Product | Mellow's role | Other parties | Deposit assets | Chain | Details | | - | - | - | - | - | - | | Lido EarnETH | Infrastructure; appointed to provide curation services | Lido – product owner and distribution | ETH, WETH, stETH, wstETH | Ethereum | [EarnETH](/lido-earn/earneth) | | Lido EarnUSD | Infrastructure; appointed to provide curation services | Lido – product owner and distribution | USDC, USDT | Ethereum; underlying vaults also on Base, Plasma and Mantle | [EarnUSD](/lido-earn/earnusd) | | USDG Yield Vault | Infrastructure; appointed to provide curation services | Robinhood Chain; USDG issued by Paxos | USDG | Robinhood Chain | [USDG Yield Vault](/usdg-vault/overview) | Reviewed 29 September 2026. # DVV Deployment Source: https://docs.mellow.finance/dvsteth-vault/dvv-deployment Legacy: DVstETH contract addresses: vault, asset, admin, proxy admin, and curator on Ethereum mainnet. **Legacy architecture.** DVstETH is an earlier generation of Mellow vault infrastructure, documented for existing deployments. [Core Vaults](/core-vaults/overview) are Mellow's current vault architecture. | Parameter | Value | | - | - | | Type | dvv | | Vault | [0x5E362eb2c0706Bd1d134689eC75176018385430B](http://eth.blockscout.com/address/0x5E362eb2c0706Bd1d134689eC75176018385430B) | | Asset | [0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0](http://eth.blockscout.com/address/0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0) | | VaultAdmin | [0x9437B2a8cF3b69D782a61f9814baAbc172f72003](http://eth.blockscout.com/address/0x9437B2a8cF3b69D782a61f9814baAbc172f72003) | | VaultProxyAdmin | [0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0](http://eth.blockscout.com/address/0x81698f87C6482bF1ce9bFcfC0F103C4A0Adf0Af0) | | ProxyAdmin | [0x8e6c80c41450d3fa7b1fd0196676b99bfb34bf48](http://eth.blockscout.com/address/0x8e6c80c41450d3fa7b1fd0196676b99bfb34bf48) | | Curator | [0x9437B2a8cF3b69D782a61f9814baAbc172f72003](http://eth.blockscout.com/address/0x9437B2a8cF3b69D782a61f9814baAbc172f72003) | | Name | Decentralized Validator Token | | Symbol | DVstETH | # Overview Source: https://docs.mellow.finance/dvsteth-vault/overview Legacy: Wrapped liquid staking token powered by Lido wstETH with DVT incentive layer. Covers Obol and SSV incentive calculation, claiming process, withdrawal mechanics, and FAQ. **Legacy architecture.** DVstETH is an earlier generation of Mellow vault infrastructure, documented for existing deployments. [Core Vaults](/core-vaults/overview) are Mellow's current vault architecture. DVstETH is a wrapped Liquid Staking Token powered by the Lido protocol's wstETH, allowing vault depositors to reuse their staking receipts across the DeFi ecosystem. Distributed Validator Technology (DVT) enables splitting of a validator's private key into multiple key shards, which are distributed among an independent cluster of nodes. This means no single node has access to the entire key, greatly enhancing validator key security. DVstETH users receive token incentives from the DVT providers whose validators are active in the Lido Protocol via the Simple DVT and Community Staking modules. As more DVT based validators are activated via Lido, the vault will accrue an increasing amount of incentives. ### FAQ **How is the total number of incentives for Obol and SSV Network calculated?** Users will receive incentives by depositing (W)ETH into the Vault, with the number of incentives calculated based on how much stake they have in the Vault in relation to total stake in the Vault and taking into account how long the user's stake has been in the Vault. Incentives calculations also take into account the Distribution Rules mentioned below. Incentives for both Obol and SSV are calculated based on the validator performance for their respective validators within the Lido protocol on a daily basis. Vault stakers that follow the Distribution Rules explained above will receive an allocation of incentives based on the amount of ETH staked via the Vault and time ETH remains staked within the Vault. More info on SSV and Obol incentives [here](../points/overview). **How do I claim the incentives accrued to my vault position?** SSV incentives are claimable monthly, in line with their existing [Incentivized Mainnet Program](https://ssv.network/incentivized-mainnet/). Users will be able to claim their incentives via [SSV Rewards](https://www.ssvrewards.com/) following the distribution of incentives as noted on the [SSV Governance Forum](https://forum.ssv.network/t/incentivized-mainnet-program-distributions/1256/30). Obol launched a community airdrop in January 2025 that provided a 7.5% community allocation. Recently, Obol has launched their [Incentives Program](https://obol.org/incentives), with 2.5% of the Obol token supply eligible each year. Obol incentives are currently being accrued to users, however are not yet claimable. Additional information will be provided when users can claim incentives.