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. USDC is the base leg of all four cores - stable, FX, crypto and stocks (Pool Composition) - 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). 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).
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 by design; 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:
- The interface is three functions.
IAdapterisallocate,deallocate,realAssets. - No permission is required.
addAdapteris gated only whenadapterRegistryis set; with it left ataddress(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. - Losses pass through.
realAssets()feedsaccrueInterestView(), wherenewTotalAssets = min(realAssets, maxTotalAssets)and the ceiling comes frommaxRate. 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).
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, 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 , 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 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). 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), 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 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. 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 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) |
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 four cores and why USDC is base in each
- Providing Liquidity: the LP receipt, its index, the cooldown and the exit haircut
- Inventory Management: coverage and the haircut it drives
- Hooks: the outbound yield hooks and their venue configuration
- Total Return Swaps: the other consumer of coverage as a demand signal