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 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. An asset such as USDC is typically listed on several cores (Pool Composition), so that choice is several 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). Since a same-asset exit is capped in kind at the leg’s coverage, a single-leg LP who needs their own token back carries that core’s correlated swing.
Cores are cut along asset-class lines because their flows differ. A vault spread across several diversifies the one risk a single-leg position cannot, without requiring the depositor to predict 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).
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 | Selected. Arbitrary-protocol adapters, no permission required, losses representable. |
| Euler Earn | Rejected. submitCap gates on isStrategyAllowed, which resolves to perspective-verified EVK vaults or same-factory Earn vaults. Arbitrary ERC-4626 is refused; admission needs Euler governance. |
| 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 | 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. IAdapter is three functions: allocate, deallocate, realAssets. No permission is required: addAdapter is gated only when adapterRegistry is set, and with it left at address(0) a vault enables its own adapters; the governance registry is opt-in and exists so a curator can abdicate that setter to prove non-custody. And losses pass through: realAssets() feeds accrueInterestView(), where newTotalAssets = min(realAssets, maxTotalAssets) and the ceiling comes from maxRate, so gains are rate-capped while losses deflate share price directly. An LP loss at the pool rate therefore needs no special casing, which a 4626-only aggregator cannot express.
Fourteen audit reports are published for the framework, dated 2025-05 to 2026-08 (/audits).
3. The adapter
One adapter contract, parameterised by pool. 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, at the exit rate |
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 why the adapter model fits where a 4626-only aggregator would have needed a wrapper first.
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 the current BTR contracts.
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 returns the same-asset payout at min(c_leg, C) (PoolLiquidityLib.previewWithdraw); Pool.poolSolvencyWad() returns C. The exit rate is priceable in a view. |
deallocate is exact-asset-out; Pool.withdraw is shares-in | The payout is linear in face: amount = face · min(c_leg, C) / WAD, with both rates fixed by pre-trade state. The inversion is closed-form from one getAsset read and one poolSolvencyWad 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 , 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): 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 fee credits 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 (rehypothecation). MorphoBlueYieldHook is a shipped hook, and the YieldHook base invests the share 1 - L(R) of a leg’s reserves (dual ledger), where the liquid-share curve L(R) falls to its lMinBps floor on large reserves (a per-hook parameter). 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. On a large leg that is up to 1 - lMinBps of the vault’s own deposit looped back out.
Either disable the outbound hook on any asset carrying an inbound vault, or pin the two to provably disjoint venues. A hook’s venue is per-chain configuration (Hooks), so this is a deployment invariant, not a code change.
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 into PT and YT. The straightforward version is not viable.
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 and write-downs on the leg, and with the pool rate , which any leg’s real loss lowers. Pool.hookWriteDown keeps the index at or above 1, but a total loss on the leg can still take its claim to dust.
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. An AMM-LP-backed vault oscillates, so every transient peak becomes the permanent watermark.
A generic SY wrapper would also misreport. PendleERC4626SY derives exchangeRate() from totalAssets / totalSupply. A redemption at min(c_leg, C) 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.
A viable path needs a bespoke, audited SY whose exchangeRate is a non-decreasing, conservative floor: exit-rate-adjusted, ratcheting only on realised and non-reversible fee accrual, with pool-rate 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) |
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: the cores, their rosters and the base each quotes against
- Providing Liquidity: the LP receipt, its index, the cooldown and the exit rate
- Inventory Management: coverage and the pool rate
- Hooks: the outbound yield hooks and their venue configuration
- Total Return Swaps: the other consumer of coverage as a demand signal