For the complete documentation index, see llms.txt. This page is also available as Markdown.

Meta balance adapters

One immutable adapter per protocol, serving many instances via a governed on-chain instance set.

A meta-adapter is a single immutable contract that reports positions for many instances of one protocol — every Morpho vault, every Aave market, every Balancer pool — instead of one deployed adapter per instance. It is the model used for all high-cardinality protocols.

Source: MetaBalanceAdapter.sol


Why

The original model deployed one adapter contract per pool / market / vault: covering N Morpho vaults meant N immutable contracts, each a CREATE deploy plus a governance registration. For protocols with many fungible instances this scaled badly — every new pool was a deploy, an audit-surface delta, and a governance action.

The framework's core rule is that adapters are immutable — no Ownable, no admin functions. So the instance set can't live (mutably) inside the adapter. Instead:

The adapter stays a pure function of (account, instance) over any instance of its protocol. The governed "which instances do we actually query" set lives on the NAVCalculator — which is already upgradeable and MANAGER-gated — and is passed to the adapter at query time.

This keeps every meta-adapter immutable while making the instance set a governed, on-chain registry.


How it works

  • IMetaBalanceAdapter extends IBalanceAdapter and is keyed by a bytes32 instance coordinate. Its inherited getAdapterPositions(account, assetFilter) reads the adapter's own NAV-configured set via NAV_CALCULATOR.getMetaInstances(address(this)) and fans out over it — so the NAV Calculator's read path is unchanged (no meta-specific branch).

  • MetaBalanceAdapter is the abstract base. It provides ERC-165, the registered-asset helper, and per-instance fan-out with per-instance revert isolation — a single bad/unknown instance coordinate is caught and skipped, and cannot zero out the rest of the batch. An instance that instead runs out of gas is not absorbed: the fan-out raises InstanceGasExhausted(adapter, instance, stipend, consumed), because recording a starved instance as an empty leg list would drop its debt leg and over-report NAV behind a complete-looking answer. See a starved read is not an empty read. Concrete adapters implement two hooks (_instancePositions, _instanceAssets) and optionally _instancePositionId.

  • Deploy-time compatibility check. The base constructor probes the NAVCalculator it is given for getRegisteredAsset and reverts NAVCalculatorTooOld(navCalculator) if it cannot serve it — meta-adapters rely on that O(1) lookup, and pointing one at an older implementation would otherwise fail later, at read time. The guard binds the next adapter deployed, not those already live.

  • Instance coordinate (bytes32). For most protocols this is an address widened to bytes32 (a vault, pool, gauge, or data-provider address). For protocols whose native identifier is already 32 bytes it is used raw — Morpho market ids and Balancer V2 pool ids.

  • Identity is derived on-chain from the coordinate: positionId = abi.encode(vault) and the 3-level taxonomy (protocolBrand / protocolId / protocolSubId) set from ProtocolIds.sol. Display labels are produced lazily — the adapter's positionLabels(positionId) derives the breadcrumb (e.g. from the vault's ERC-20 name() or the pool's coin symbols) on the verbose read path only. Nothing instance-specific is stored in config — see Position identity.


Registration

Meta-adapters are registered and curated by the governance Safe (MANAGER role) on the NAV Calculator. The adapter contract is immutable; the instance set is the governed, mutable part, with its own add/remove lifecycle:

Action
Call

Register the adapter + seed instances (atomic)

addMetaBalanceAdapter(adapter, instances)

Add instances (extend coverage)

addMetaInstances(adapter, instances)

Remove instances (drop coverage)

removeMetaInstances(adapter, instances)

Remove the adapter entirely

removeBalanceAdapters([adapter])

Inspect the configured set

getMetaInstances(adapter) (view)

addMetaInstances reverts DuplicateMetaInstance on a coordinate already present or repeated in the batch (a duplicate would double-count NAV); removeMetaInstances reverts MetaInstanceNotFound if a coordinate isn't present; both revert NotMetaAdapter if the target isn't a meta-adapter, and on an empty list. All are all-or-nothing. Full detail: NAV Calculator Admin / Manager API.

Example — the configured instance set of the Morpho Vaults meta-adapter (illustrative; each bytes32 is a vault address widened to 32 bytes):

For raw-bytes32 adapters (Morpho Markets, Balancer V2 Pools) the coordinates are the native ids themselves, not widened addresses.

Adding a new pool/vault/market is therefore a single addMetaInstances transaction — not a contract deploy.

Plain (single-scope) adapters use addBalanceAdapters instead and have no instance set. See Balance adapters. A separate assisted-adapter family (ether.fi exit, StakeWise exit, Nexus Mutual) caches position coordinates the owner feeds permissionlessly on-chain (msg.sender-keyed, no roles) — distinct from the meta model here.


Meta-adapter catalogue

Adapter
Protocol
Instance key
Positions

Aave V3

Aave V3

Data provider (addr)

Supply · Collateral · Borrow

Morpho Markets

Morpho Blue

bytes32 market id (raw)

Collateral · Supply · Borrow

Morpho Vaults

Morpho

Vault (addr)

Vault shares → assets

Gearbox Markets

Gearbox V3

Farming pool (addr)

Pool shares → assets

Gearbox Credit Accounts

Gearbox V3

Credit Account (addr)

Borrowed (debt) · Collateral

Euler Vaults

Euler

Vault (addr)

Supply/Collateral · Borrow

Fluid fTokens

Fluid

fToken (addr)

Supply → assets

Fluid Vaults

Fluid

Vault (addr)

Collateral · Borrow

Compound V3

Compound V3

Comet (addr)

Supply · Borrow · Collateral · Rewards

StakeWise V3

StakeWise

Vault (addr)

Staking · osToken debt

Balancer V3 Pools

Balancer V3

Pool (addr)

Pro-rata pool tokens

Balancer V3 Gauges

Balancer V3

Gauge (addr)

Staked pool tokens · Rewards

Balancer V2 Pools

Balancer V2

bytes32 pool id (raw)

Pro-rata pool tokens

Balancer V2 Gauges

Balancer V2

Gauge (addr)

Staked pool tokens · Rewards

Curve Pools

Curve

Pool (addr)

Pro-rata pool coins

Curve Gauges

Curve

Gauge (addr)

Staked coins · CRV + extra rewards

Convex

Convex

Reward pool (addr)

Staked coins · CRV/CVX + extras

Uniswap V2

Uniswap V2

Pair (addr)

Pro-rata pair tokens

Uniswap V3

Uniswap V3

Pool (addr)

In-range liquidity (Supplied) · Fees, per LP NFT

PancakeSwap V2

PancakeSwap V2

Pair (addr)

Pro-rata pair tokens

PancakeSwap V3

PancakeSwap V3

Pool (addr)

In-range liquidity (Supplied) · Fees, per LP NFT

The Uniswap V3 / PancakeSwap V3 meta-adapters discover an account's LP NFTs on-chain (the NonfungiblePositionManager is ERC721Enumerable) and value them per configured pool. They link the GPL-2.0-or-later Uniswap V3 math libraries, so those two contracts are GPL-2.0-or-later rather than the repo's default BUSL-1.1.

Last updated