---
title: "Single-Asset Vaults"
description: "One vault per asset allocating across every pool that lists it, built as a Morpho Vaults V2 adapter; the allocation signal, the circularity rule, and an honest assessment of Pendle tokenization"
audience: both
type: reference
status: design
lang: en
updated: "2026-09-06"
publish: true
---

# Single-Asset Vaults

> **Roadmap - not built.** No vault or adapter is deployed. The pools, their LP receipts and every read this design depends on are live; the aggregation layer above them is not. Sections marked *verified* are checked against deployed BTR code or against a named external repository, docs page or audit. [§6](#6-pendle-tokenization-is-not-currently-viable) is a negative finding and is stated as one.

A depositor who wants BTR yield on a single asset today has to choose a pool and stay in it. USDC is the base leg of **all four cores** - stable, FX, crypto and stocks ([Pool Composition](/docs/2-3-pool-composition#1-the-cores)) - so that choice is four separate positions, each rebalanced by hand.

A single-asset vault collapses that into one deposit. It holds USDC across every pool that lists it and moves the allocation as demand moves.

---

## 1. Why aggregate at all

The case is not only convenience. A leg's coverage is moved by its **whole core**, and correlated legs can move together and leave every leg in a core on the same side at once ([Providing Liquidity](/docs/4-2-providing-liquidity#1-what-a-position-is)). Since the exit haircut reads coverage, a single-leg LP carries the whole of that core's correlated swing.

The four cores are cut along asset-class lines precisely because their flows differ. A vault spread across all four diversifies the one risk a single-leg position cannot diversify at all, and it does so without the depositor forming a view on which core will be in demand.

The second effect is allocative. Demand for liquidity is not uniform across cores, and the vault moves capital toward whichever core is short of it - which is also where it is paid best ([§4](#4-allocation-policy)).

---

## 2. Framework selection

*Verified against the repositories and docs named.*

The allocator is not written from scratch. Four candidates were assessed against one requirement: allocate a single asset into a venue that is **not** a lending market and whose position value **can fall**.

| Framework | Verdict |
|---|---|
| **[Morpho Vaults V2](https://github.com/morpho-org/vault-v2)** | **Selected.** Arbitrary-protocol adapters, no permission required, losses representable. |
| [Euler Earn](https://github.com/euler-xyz/euler-earn) | Rejected. `submitCap` gates on `isStrategyAllowed`, which resolves to perspective-verified EVK vaults or same-factory Earn vaults. Arbitrary ERC-4626 is refused by design; admission needs Euler governance. |
| [Morpho Blue](https://github.com/morpho-org/morpho-blue) | Wrong primitive. A market is `{loanToken, collateralToken, oracle, irm, lltv}`. BTR pools have no borrower, no LLTV and no liquidation; there is nothing to express. |
| [Aave v4](https://aave.com/blog/aave-v4-live-ethereum) | Unsuitable. Live on Ethereum since 2026-03-30, hub-and-spoke, but a Spoke is a governance-added lending market. No curated-vault or external-strategy concept exists. |

Morpho Vaults V2 replaced MetaMorpho's hardcoded Blue-market allocation with an adapter model. Three properties decide it:

- **The interface is three functions.** [`IAdapter`](https://github.com/morpho-org/vault-v2/blob/main/src/interfaces/IAdapter.sol) is `allocate`, `deallocate`, `realAssets`.
- **No permission is required.** `addAdapter` is gated only when `adapterRegistry` is set; with it left at `address(0)` a vault enables its own adapters. The [governance registry](https://docs.morpho.org/curate/concepts/adapter-registry/) is opt-in and exists so a curator can *abdicate* that setter to prove non-custody.
- **Losses pass through.** `realAssets()` feeds `accrueInterestView()`, where `newTotalAssets = min(realAssets, maxTotalAssets)` and the ceiling comes from `maxRate`. Gains are rate-capped; **losses deflate share price directly**. The coverage haircut therefore needs no special casing - and this is the property a 4626-only aggregator cannot express.

Fourteen audit reports are published for the framework, dated 2025-05 to 2026-08 ([/audits](https://github.com/morpho-org/vault-v2/tree/main/audits)).

---

## 3. The adapter

One adapter contract, parameterised by pool. [`MorphoVaultV1Adapter.sol`](https://github.com/morpho-org/vault-v2/blob/main/src/adapters/MorphoVaultV1Adapter.sol) is the template at roughly 145 lines.

| Function | Behaviour |
|---|---|
| `allocate` | Decode the target pool from `data`, `Pool.deposit(asset, amount)`, return the position-value delta and the leg's id |
| `deallocate` | Convert the requested assets to LP shares, `Pool.withdraw(...)`, leave the vault an approval for **at least** `assets` |
| `realAssets` | Value of held LP shares in the underlying, haircut included |

No intermediate ERC-4626 wrapper per leg is required; the adapter calls `Pool` directly. BTR's LP receipt is a plain ERC-20 (`LPToken is ERC20`), not a 4626 vault, which is exactly why the adapter model fits where a 4626-only aggregator would have needed a wrapper first.

**Ids and caps.** An id is `keccak256(idData)` with the data adapter-defined, described upstream as "an abstract identifier for a common risk factor". One id per `(pool, asset)` leg gives per-pool `absoluteCap` and `relativeCap` enforcement on allocation, with cap *decreases* exempt from timelock and available to the sentinel role. That is the exposure-limiting half of an allocator, obtained without writing one.

**Roles** are owner, curator, allocator and sentinel, with most curator actions timelocked (0–3 weeks, per function).

### 3.1. Integration details, resolved

*Verified against deployed BTR code.*

Three hazards are specific to putting an AMM behind this interface. Two are already answered by the pool's design:

| Hazard | Resolution |
|---|---|
| `realAssets()` is `view`, and is the sole loss channel | `Pool.previewWithdraw` is `external view` and applies `applyHaircut` directly (`PoolLiquidity.previewWithdraw`). The haircut is priceable in a view. |
| `deallocate` is exact-asset-out; `Pool.withdraw` is shares-in | The haircut is **linear** in the amount: `actualAmount = amount − ceil(amount × haircutRatio / WAD)`, with `haircutRatio` fixed by pre-trade reserves and liabilities. The inversion is closed-form from a single `getAsset` read; no over-withdraw loop. Rounding is already pool-favourable. |
| `forceDeallocate` is **permissionless** | Stands as a real constraint. Any caller supplies `data`, so `data` is hostile input: it may select a pool and nothing else. The adapter sets `minOut` and `deadline` itself, never from `data`. |

Two further pool-side facts the adapter must respect: `Pool.deposit` carries no `minOut` and no deadline, so the allocator supplies its own guard against a moving mark; and `maxRedeem` folds halt state, the anti-JIT frozen quantity and $R_{\mathrm{liq}} - \text{minLiquidity}$, so it is the natural read for the adapter's withdrawable ceiling.

---

## 4. Allocation policy

Allocation is off-chain keeper logic calling `allocate` and `deallocate` within the caps. No new contract.

The signal is **coverage**, for the same reason it prices the wall ([Total Return Swaps §6](/docs/6-1-total-return-swaps#6-coverage-is-the-utilization-curve)): a leg below its peg is a leg short of the asset, and depositing into it raises reserves and lifts coverage back toward 1. Need and reward point the same way - a leg drawing flow it cannot cover is also the leg earning the widest spread.

Realised APR is read from the leg's liquidity index over time; it accrues through `accrueLpFee` raising `liquidityIndexWad`, so the index delta is the return, and there is no on-chain APR getter to read instead. Everything the policy needs - `getAsset`, `getCoverageRatio`, `getLiquidReserves`, `previewWithdraw` - is already exposed.

Rebalancing is rate-limited rather than continuous. Each move pays the spread on both legs of the round trip, so a policy that chases small coverage differentials will underperform one that does not.

---

## 5. The circularity rule

> **HARD RULE. An asset with an inbound vault must not run an outbound yield hook into the same venue.**

BTR pools already deploy idle reserves *outward*. `MorphoBlueYieldHook` is a shipped hook, and the `YieldHook` base targets ~65% invested with ±5% hysteresis ([dual ledger](/docs/glossary#dual-ledger)). Point a Morpho vault *inward* at the same asset and the path closes on itself: vault → pool → Morpho market → vault. The result is recursive TVL, yield counted twice, and a withdrawal path that deadlocks against its own liquidity.

At the default target that is 65% of the vault's own deposit looping straight back out.

Either disable the outbound hook on any asset carrying an inbound vault, or pin the two to provably disjoint venues. Hook venues are constructor arguments and per-chain configuration ([Hooks](/docs/5-1-3-hooks)), so this is a deployment invariant, not a code change - which is precisely why it has to be written down.

---

## 6. Pendle tokenization is not currently viable

*Verified against Pendle documentation, source and audit.*

The intent was that vault shares, being liquid and yield-bearing, could be tokenized on [Pendle](https://docs.pendle.finance/) into PT and YT. On the evidence, the straightforward version of that is disqualified, and it is worth stating plainly rather than listing it as future work.

**Pendle requires up-only yield, explicitly.** The community listing guide states the requirement as "The yield generated should not go negative (**\"up-only\" yield**)". The ChainSecurity audit of Pendle V2 Core records the same as a system assumption - "The PY system assumes that the exchange rate can only increase" - and, under trust assumptions, that SYs "cannot have an exchange rate that decreases as this could lead to insolvency".

**BTR vault shares are not up-only.** Value per LP share moves with fees, donations, yield, decay and write-downs, and `Pool.hookWriteDown` drives it to zero once a leg's reserves are gone - terminally, with the receipt then unburnable (`LPToken.sol`). The exit haircut moves it down in the ordinary case, long before that boundary.

**The mechanism is a permanent ratchet.** Pendle's PY index is `max(SY.exchangeRate(), storedIndex)`. Once set, a watermark never falls. When the live rate sits below it, [PT redeems for less than one unit of the accounting asset and YT stops accruing until the rate recovers](https://docs.pendle.finance/pendle-v2/ProtocolMechanics/NegativeYield). An AMM-LP-backed vault oscillates by construction, so every transient peak is baked in permanently - the worst possible shape for a ratchet.

**A generic SY wrapper would also misreport.** [`PendleERC4626SY`](https://github.com/pendle-finance/Pendle-SY-Public) derives `exchangeRate()` from `totalAssets / totalSupply`. A haircut applied at redemption is invisible to that ratio, so PT and YT accounting would diverge from actual redeemable value.

Listing itself is not the obstacle - deployment is effectively self-service, with Pendle-side whitelisting for UI and incentives, and the deployer signs an on-chain message assuming responsibility for contract security.

**What a viable path would require.** A bespoke, audited SY whose `exchangeRate` is a genuinely non-decreasing, conservative floor: haircut-adjusted, ratcheting only on realised and non-reversible fee accrual, with haircut and inventory variance held outside the tokenized leg. That is a separate design with its own audit, not a wrapper over the vault. It is not scoped here, and no work on it should start before the vault itself has a live track record to floor against.

---

## 7. Build scope

| Component | Status |
|---|---|
| Pools, LP receipts, `previewWithdraw`, `maxRedeem`, coverage reads | Built, live |
| Morpho Vaults V2 framework | Built and audited upstream, not deployed by BTR |
| BTR `IAdapter` implementation | Not built - one contract, ~150 lines |
| Vault deployment, roles, caps per leg | Not built - configuration |
| Allocator keeper | Not built - off-chain policy over existing reads |
| Pendle SY | Not scoped ([§6](#6-pendle-tokenization-is-not-currently-viable)) |

The whole of the new on-chain surface is one adapter. Everything above it is an audited framework configured, and everything below it is already live.

---

## 8. Related documentation

- [Pool Composition](/docs/2-3-pool-composition): the four cores and why USDC is base in each
- [Providing Liquidity](/docs/4-2-providing-liquidity): the LP receipt, its index, the cooldown and the exit haircut
- [Inventory Management](/docs/1-1-1-inventory-management): coverage and the haircut it drives
- [Hooks](/docs/5-1-3-hooks): the outbound yield hooks and their venue configuration
- [Total Return Swaps](/docs/6-1-total-return-swaps): the other consumer of coverage as a demand signal
