> For the complete documentation index, see [llms.txt](https://docs.kpk.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kpk.io/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters.md).

# Meta balance adapters

One immutable adapter per protocol, serving many instances via a governed onchain 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:

{% hint style="info" %}
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.
{% endhint %}

This keeps every meta-adapter immutable while making the instance set a governed, onchain 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](/funds/infrastructure/architecture/onchain-accounting/balance-adapters.md#how-an-adapter-is-built). 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 onchain** 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](/funds/infrastructure/architecture/onchain-accounting/concepts/position-identity.md).

## Registration

Meta-adapters are registered and curated on the NAV Calculator by the holders of the `MANAGER` role: the NAV timelock and the Security Council Safe (the Governance Safe on Robinhood Chain), plus `AssetKindRegistry`, which holds it so that it can register, classify, and label an asset in one transaction. 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 (clears its set)   | `removeBalanceAdapters([adapter])`          |
| **Inspect** the configured set                     | `getMetaInstances(adapter)` (view)          |

```solidity
// Register the meta-adapter and seed its instance set atomically.
function addMetaBalanceAdapter(address adapter, bytes32[] calldata instances) external;

// Adjust the queried set later (all-or-nothing; rejects duplicates / unknown coordinates).
function addMetaInstances(address adapter, bytes32[] calldata instances) external;
function removeMetaInstances(address adapter, bytes32[] calldata instances) external;

// Inspect the configured set.
function getMetaInstances(address adapter) external view returns (bytes32[] memory);
```

**Each coordinate has at most one owning adapter:**

* `addMetaInstances` reverts `DuplicateMetaInstance` if this adapter already holds a coordinate or it is repeated in the batch (a duplicate would double-count NAV), and `MetaInstanceClaimedByAnotherAdapter(instance, claimedBy)` if a different adapter holds it.
* Adding a coordinate that is a registered token also removes that token from the [default adapter](/funds/infrastructure/architecture/onchain-accounting/balance-adapters/erc20-default.md)'s inclusion list, in the same transaction.
* `addMetaBalanceAdapter` always starts the adapter from an empty set, and `removeBalanceAdapters` clears the whole set.
* Removing a coordinate does **not** put the token back on the default adapter's list; it is reported by nobody until re-included with `includeAssetInDefaultAdapter`.

`removeMetaInstances` reverts `MetaInstanceNotFound` if a coordinate isn't present. `addMetaInstances` and `removeMetaInstances` 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](/funds/infrastructure/architecture/onchain-accounting/contracts/nav-calculator.md#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):

```
getMetaInstances(0x72a0…e54f)   // MorphoVaultsMeta
→ [
    0x000000000000000000000000beef…0010,   // vault 0xBEEF…0010
    0x000000000000000000000000beef…01a4,   // vault 0xBEEF…01A4
    0x000000000000000000000000d63070…b15c   // vault 0xD630…B15C
  ]
```

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.

{% hint style="warning" %}
**Trust model.** `MANAGER` can add an arbitrary instance coordinate, but a malicious or incorrect instance can only mis-report through the adapter's own read path (e.g. a fake ERC-4626 that lies about `convertToAssets`); it cannot fabricate values out of band, and NAV still drops positions whose asset is unregistered. Instance curation is a governance responsibility, index → whitelist → `MANAGER` transaction, mirroring how price feeds and plain adapters are added.
{% endhint %}

{% hint style="info" %}
Plain (single-scope) adapters use `addBalanceAdapters` instead and have no instance set. See [Balance adapters](/funds/infrastructure/architecture/onchain-accounting/balance-adapters.md). A separate [assisted-adapter family](/funds/infrastructure/architecture/onchain-accounting/assisted-balance-adapters.md) (ether.fi exit, StakeWise exit, Nexus Mutual) caches position coordinates the owner feeds permissionlessly onchain (`msg.sender`-keyed, no roles), distinct from the meta model here.
{% endhint %}

## Meta-adapter catalogue

| Adapter                                                                                                                           | Protocol       | Instance key              | Positions                                                   |
| --------------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------- | ----------------------------------------------------------- |
| [Aave V3](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/aave-v3.md)                                 | Aave V3        | Data provider (addr)      | Supply · Collateral · Borrow                                |
| [Morpho Markets](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/morpho-market.md)                    | Morpho         | `bytes32` market id (raw) | Collateral · Supply · Borrow                                |
| [Morpho Vaults](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/morpho-vault.md)                      | Morpho         | Vault (addr)              | Vault shares → assets                                       |
| [Gearbox Markets](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/gearbox.md)                         | Gearbox V3     | Farming pool (addr)       | Pool shares → assets                                        |
| [Gearbox Credit Accounts](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/gearbox-credit-accounts.md) | Gearbox V3     | Credit Account (addr)     | Borrowed (debt) · Collateral                                |
| [Euler Vaults](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/euler-vaults.md)                       | Euler          | Vault (addr)              | Supply/Collateral · Borrow                                  |
| [Fluid fTokens](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/fluid-ftokens.md)                     | Fluid          | fToken (addr)             | Supply → assets                                             |
| [Fluid Vaults](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/fluid-vaults.md)                       | Fluid          | Vault (addr)              | Collateral · Borrow                                         |
| [Compound V3](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/compound-v3.md)                         | Compound V3    | Comet (addr)              | Supply · Borrow · Collateral · Rewards                      |
| [StakeWise V3](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/stakewise-v3.md)                       | StakeWise      | Vault (addr)              | Staking · osToken debt                                      |
| [Balancer V3 Pools](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/balancer-v3-pools.md)             | Balancer V3    | Pool (addr)               | Pro-rata pool tokens                                        |
| [Balancer V3 Gauges](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/balancer-v3-gauges.md)           | Balancer V3    | Gauge (addr)              | Staked pool tokens · Rewards                                |
| [Balancer V2 Pools](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/balancer-v2-pools.md)             | Balancer V2    | `bytes32` pool id (raw)   | Pro-rata pool tokens                                        |
| [Balancer V2 Gauges](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/balancer-v2-gauges.md)           | Balancer V2    | Gauge (addr)              | Staked pool tokens · Rewards                                |
| [Curve Pools](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/curve-pools.md)                         | Curve          | Pool (addr)               | Pro-rata pool coins                                         |
| [Curve Gauges](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/curve-gauges.md)                       | Curve          | Gauge (addr)              | Staked coins · CRV + extra rewards                          |
| [Convex](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/convex.md)                                   | Convex         | Reward pool (addr)        | Staked coins · CRV/CVX + extras                             |
| [Uniswap V2](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/uniswap-v2.md)                           | Uniswap V2     | Pair (addr)               | Pro-rata pair tokens                                        |
| [Uniswap V3](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/uniswap-v3.md)                           | Uniswap V3     | Pool (addr)               | In-range liquidity (Supplied) · Fees, per LP NFT            |
| [PancakeSwap V2](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/pancakeswap-v2.md)                   | PancakeSwap V2 | Pair (addr)               | Pro-rata pair tokens                                        |
| [PancakeSwap V3](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/pancakeswap-v3.md)                   | PancakeSwap V3 | Pool (addr)               | In-range liquidity (Supplied) · Fees, per LP NFT            |
| [Wallet Yield](/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters/wallet-yield.md)                       | 17 protocols   | Token (addr)              | Wallet-held yield token (Staking · Supplied)                |
| [Symbiotic Vaults](/funds/infrastructure/architecture/onchain-accounting/balance-adapters/symbiotic-vaults.md)                    | Symbiotic      | Vault (addr)              | Vault shares redeemed to the underlying (Supplied)          |
| [Polygon Staking](/funds/infrastructure/architecture/onchain-accounting/balance-adapters/polygon-staking.md)                      | Polygon PoS    | ValidatorShare (addr)     | Delegated principal (Staking) · Unclaimed rewards (Rewards) |

{% hint style="info" %}
**Polygon Staking is an Ethereum mainnet adapter.** Polygon PoS staking settles on L1 via the `StakeManager`, which mints one `ValidatorShare` per validator, so the instance coordinate is an L1 contract and nothing about it runs on the Polygon chain.
{% endhint %}

{% hint style="info" %}
The Uniswap V3 / PancakeSwap V3 meta-adapters discover an account's LP NFTs onchain (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.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.kpk.io/funds/infrastructure/architecture/onchain-accounting/meta-balance-adapters.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
