# Multi-Vehicle architecture \[How Multi-Vehicles aggregate yield across multiple yield sources]

:::info
In user-facing documentation, a MultiVehicle implements an **Allocation Strategy**. See [Glossary](/developers/glossary).
:::

A MultiVehicle is itself a STEAM Vehicle that holds positions in other Vehicles, so a Conduit distributing one sees the same interface as a Conduit distributing an Aave V3 Vehicle. It exists when a product needs several yield sources behind one share token. One source needs no MultiVehicle.

## Components

The MultiVehicle exposes only `manager()`. Everything else hangs off the VehicleManager.

```solidity [Solidity]
IVehicleManager manager = MultiVehicle(multiVehicle).manager();

ISectorAccountingEngine accounting = manager.accountingEngine();
IQueueStrategyEngine strategy      = manager.queueStrategyEngine();
ISubQueryEngine subQueryEngine     = manager.subQueryEngine();
IQueryRedeemQueue redeemQueue      = manager.redeemQueue();
```

| Component | Owns |
|-----------|------|
| `MultiVehicle` | The user-facing STEAM surface (`create`, `resume`, `unlock`, `recover`) and the ERC20 share token. |
| `VehicleManager` | Sub-vehicle authorization and configuration, thresholds, `maxTotalAssets`, and the wiring to the engines. |
| `SectorAccountingEngine` | Double-entry [sector accounting](/developers/contracts/accounting): every asset and share movement, `totalAssets()`, `withdrawable()`. |
| `QueueStrategyEngine` | The deposit and redeem queues that decide where capital goes. |
| `SubQueryEngine` | The STEAM lifecycle of sub-queries dispatched to sub-vehicles, and their ephemeral accounting. |
| `QueryRedeemQueue` | Redemption demands that exceed immediate liquidity. |

## The queues

The deposit queue is an ordered list of `{vehicle, target}` pairs where `target` is a ceiling: fill index 0 up to its target, then move on, stopping when assets run out or a vehicle hits `maxDepositable`.

```text
depositQueue    = [{aave, 60k}, {morpho, 30k}]
currentHoldings = {aave: 50k, morpho: 20k}
newDeposit      = 20k shares

aave:   50k < 60k  -> allocate 10k, aave at 60k
morpho: 20k < 30k  -> allocate the remaining 10k, morpho at 30k
```

The redeem queue has the same shape, but `target` is a floor: redeem index 0 down to its floor, then continue to the next entry until the request is covered.

```text
redeemQueue     = [{aave, 20k}, {morpho, 10k}]
currentHoldings = {aave: 60k, morpho: 30k}
redeemRequest   = 50k shares

aave:   60k - 20k floor -> redeem 40k, aave at floor
morpho: 30k - 10k floor -> redeem the remaining 10k, morpho at 20k
```

Neither queue enforces an ongoing ratio. A vehicle that grows past its target on yield alone is simply skipped by new deposits.

## Authorization and caps

A sub-vehicle must be authorized on the VehicleManager before it can receive capital. Authorization checks that it is a contract, reports `ready()`, and uses the same base asset.

```solidity [Solidity]
vehicleManager.authorizeAndConfigure(
    vehicle,
    VehicleManagerStore.VehicleConfig({
        mode: VehicleMode.Automatic,
        cap: Target({ value: 100_000e18, threshold: 0 })
    })
);

vehicleManager.configure(existingVehicle, newConfig);
vehicleManager.unauthorize(vehicleToRemove);   // redeem positions first
```

`Automatic` vehicles take part in queue-based allocation; `Manual` ones only receive capital through an explicit `move` plus `dispatch`. The cap limits exposure to one source regardless of queue targets or manual moves. Unauthorizing blocks new allocations but does not redeem what is already there.

These two settings plus role scoping are the guardrails: a platform holding `MULTI_VEHICLE_SET_VEHICLE_AUTHORIZATION` decides which sources exist and how large they get, while the asset manager allocates within that. See [Guardrails](/strategies/allocation/guardrails).

## Operating levers

There is no `rebalance` function. Compose one from `dispatch` and `move`, reusing one `operationId` so off-chain consumers can group the steps.

```solidity [Solidity]
bytes32 opId = keccak256("rebalance-aave-to-morpho");

// 1. Redeem from the source vehicle; proceeds land in AVAILABLE
accounting.dispatch(
    ISectorAccountingEngine.DispatchParams({
        vehicle: fromVehicle,
        mode: Mode.REDEEM,
        amount: shareAmount,
        settledDestination: SectorLib.AVAILABLE,
        rejectedDestination: SectorLib.ALLOCATION,
        minOutput: minAssetsOut,
        data: "",
        operationId: opId
    })
);

// 2. Stage the freed assets for the destination vehicle
accounting.move(
    ISectorAccountingEngine.MoveParams({
        from: SectorLib.AVAILABLE,
        to: SectorLib.toSector(toVehicle),
        asset: baseAsset,
        amount: type(uint256).max,
        operationId: opId
    })
);

// 3. Dispatch a DEPOSIT into the destination vehicle
```

If the source is async, step 1 returns `PROCESSING` and the assets only exist in `AVAILABLE` once the redemption settles, so steps 2 and 3 run in a later transaction.

The other three levers are one call each: `queueStrategyEngine.setQueues(depositQueue, redeemQueue)` changes allocation priority with immediate effect on new flows and none on existing positions; `vehicleManager.feedQueryRedeemQueue()` provides liquidity to pending redemption demands; `vehicleManager.setThresholds(...)` and `setMaxTotalAssets(...)` set the operational bounds.

## Async sub-vehicles

A sub-query dispatched to an async sub-vehicle (ERC-7540 vaults such as Lagoon or Centrifuge, Ethena with cooldown on) stays in `PROCESSING` until the underlying protocol is ready, while a sync one (Aave V3, Morpho Blue, ERC-4626) completes inside `dispatch`. The SubQueryEngine holds the nested lifecycle, ephemeral accounting keeps `totalAssets()` honest across the gap, and a keeper holding `MULTI_VEHICLE_PROGRESS_QUERY` advances the sub-query when it is ready. The MultiVehicle's own user-facing query can stay open until those settle.

Vehicle sector balances that stay high are the signal that sub-queries are settling slowly. See [Sync vs async operations](/developers/contracts/sync-vs-async).
