> For the complete documentation index, see [llms.txt](https://qiro.gitbook.io/qiro-vaults/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://qiro.gitbook.io/qiro-vaults/technical-overview/protocol/smart-contract-architecture.md).

# Smart Contract Architecture

The contracts a Qiro vault is built from, what each is responsible for, and how they are wired together.

A Qiro vault is not a single contract. It is a small set of contracts with one responsibility each, deployed per vault and wired together at construction.

This page describes each contract in turn. For the path capital takes through them, see [Flow of Funds](/qiro-vaults/technical-overview/protocol/flow-of-funds.md); for the economic model they implement, see [NAV & Valuation](/qiro-vaults/core-concepts/nav-and-valuation.md) and [Fees](/qiro-vaults/core-concepts/fees.md); for who is permitted to call what, see [Protocol Actors](/qiro-vaults/technical-overview/protocol/protocol-actors.md).

### Core contracts

```mermaid
flowchart TD
    INV(["Investors"])

    subgraph PROTO["Protocol contracts"]
        V["StakedVault<br/>(shares, pricing, fees)"]
        RM["RedemptionManagerFifoQueue<br/>(redemption queue)"]
        VSM["VaultStrategyManager<br/>(capital allocation)"]
        PM["SubaccountPositionManager<br/>(deployment and NAV)"]
    end

    subgraph OUT["Outside the contracts"]
        SA["Subaccount wallets<br/>(EOA / multisig)"]
        SPV["Deal SPVs<br/>(bankruptcy-remote legal lender)"]
        B["Borrowers"]
        LIQ["Liquid strategies<br/>(T-bills, lending protocols)"]
    end

    INV -- "deposit / redeem" --> V
    V <-- "requests, servicing" --> RM
    V <-- "idle cash, NAV" --> VSM
    VSM <-- "1:N" --> PM
    PM -- "invest / redeem" --> SA
    SA -- "facility funding" --> SPV
    SPV -- "drawdown / repayment" --> B
    SA -- "liquidity buffer" --> LIQ

    classDef onchain fill:#6B56F1,stroke:#6B56F1,stroke-width:0px,color:#FFFFFF;
    classDef edge fill:#4C33D9,stroke:#4C33D9,stroke-width:0px,color:#FFFFFF;
    classDef offchain fill:#1E1B3A,stroke:#6B56F1,stroke-width:2px,color:#FFFFFF;
    classDef actor fill:#0F766E,stroke:#0F766E,stroke-width:0px,color:#FFFFFF;

    class V,RM,VSM onchain;
    class PM edge;
    class SA,SPV,LIQ offchain;
    class INV,B actor;

    style PROTO fill:none,stroke:#6B56F1,stroke-width:1px,stroke-dasharray:5 5;
    style OUT fill:none,stroke:#8E8AA8,stroke-width:1px,stroke-dasharray:5 5;
```

Nothing in the lower group is a Qiro contract. Subaccount wallets are EOA or multisig wallets, Deal SPVs are legal entities, and borrowers are companies; liquid strategies may themselves be third-party contracts, but none of them are reachable by Qiro's access control. The `SubaccountPositionManager` is the last contract in the chain, and therefore the point where on-chain enforcement ends and reporting begins.

### StakedVault

The share ledger and the contract investors interact with. It is `ERC20` plus `IERC4626`, with asynchronous redemptions following ERC-7540.

It holds idle cash, issues and burns shares, maintains the two valuation bases, accrues and settles fees, enforces the investor whitelist, and can be paused.

```solidity
function deposit(uint256 assets, address receiver)
    external
    onlyWhitelisted(msg.sender)
    onlyWhitelisted(receiver)
    nonReentrant
    whenNotPaused
    returns (uint256 shares);
```

Fees settle before the price is taken, so the share price you transact at is already net of them:

```mermaid
sequenceDiagram
    actor Investor
    participant V as StakedVault

    Investor->>V: deposit(assets, receiver)
    Note right of V: sender and receiver must both be whitelisted
    V->>V: settle accrued fees
    V->>V: price at the optimistic valuation
    V-->>Investor: shares
```

### RedemptionManagerFifoQueue

Holds the redemption queue. When an investor calls `requestRedeem`, their shares move here and are held while the request is outstanding.

One Redemption Manager serves exactly one vault. Requests are serviced in submission order, sized by the operator rather than the requester, so a single request can be filled across several rounds:

```solidity
function serviceRequests(uint256 shares) external nonZeroAmount(shares) onlyOwner;
```

The three phases are separate transactions, by separate parties:

```mermaid
sequenceDiagram
    actor Investor
    participant V as StakedVault
    participant RM as RedemptionManagerFifoQueue
    actor Operator

    Investor->>V: requestRedeem(shares, controller, owner)
    V->>RM: shares held, request ID issued
    Operator->>RM: serviceRequests(shares)
    Note right of RM: shares burned at the conservative valuation
    Investor->>V: redeem() or withdraw()
    RM-->>Investor: assets
```

Three limits are enforced at request time: a minimum request size derived from the asset's decimals, a cap on active requests per owner, and a cap per controller. Claiming remains available while the vault is paused and to an address that has since been removed from the whitelist.

### VaultStrategyManager

The capital-allocation boundary, and the only contract the vault trusts to move idle cash out to strategies. One VSM serves exactly one vault; it is bound to that vault by an immutable `VAULT`.

It has three responsibilities: whitelist position managers and set a per-PM `sanctionedLimit`; route capital between the vault and its PMs; and aggregate each PM's conservative and optimistic valuation into the single NAV the vault prices against.

Its authority is split by direction of risk, which is the pattern to understand before reading [Protocol Actors](/qiro-vaults/technical-overview/protocol/protocol-actors.md):

| Direction       | Examples                    | Authority                |
| --------------- | --------------------------- | ------------------------ |
| Risk-increasing | whitelist a PM, raise a cap | `onlyTimelock` — delayed |
| De-risking      | remove a PM, lower a cap    | `onlyOwner` — instant    |

Capital movement itself is neither: `withdrawFromVault` and `depositToVault` are callable only by an already-whitelisted position manager.

Capital is therefore **pulled by the position manager**, not pushed by the vault:

```mermaid
sequenceDiagram
    actor Curator
    participant PM as SubaccountPositionManager
    participant VSM as VaultStrategyManager
    participant V as StakedVault
    participant SA as Subaccount wallet

    Curator->>PM: invest(subaccount, amount)
    PM->>VSM: withdrawFromVault(amount)
    VSM->>V: draw idle cash
    V-->>PM: assets
    PM->>SA: transfer
```

Capital returns the same way, and this is the link between a facility repaying and a redemption becoming claimable:

```mermaid
sequenceDiagram
    actor Curator
    participant SA as Subaccount wallet
    participant PM as SubaccountPositionManager
    participant VSM as VaultStrategyManager
    participant V as StakedVault

    Curator->>PM: redeem(subaccount, amount)
    PM->>SA: safeTransferFrom
    SA-->>PM: assets
    PM->>VSM: depositToVault(amount)
    VSM->>V: depositFunds(amount)
    Note right of V: lands as idle cash
```

Returned capital lands as vault idle cash, and idle cash is what the Redemption Manager draws on when the queue is serviced. A request becomes claimable because capital came back, not because a timer elapsed.

### SubaccountPositionManager

The only position manager type in use. It deploys vault capital to whitelisted subaccount wallets and surfaces a single aggregate NAV back to the VSM. One VSM may whitelist several PMs; one PM may hold several subaccounts.

What a subaccount does with that capital is not fixed by the contracts. It may fund a credit facility through a Deal SPV, or sit in liquid strategies such as T-bills or lending protocols, which is where the vault's liquidity buffer is held. See [Yield Sources](/qiro-vaults/core-concepts/yield-sources.md).

`externalAssets` is the single source of truth for deployed value. It is incremented by `invest()`, decremented by `redeem()`, and overwritten by `setNAV()`. It carries the **optimistic** NAV by design, and both valuation tiers return it unchanged, so the position manager applies no haircut of its own. The mark is posted by the curator.

```solidity
function setNAV(uint256 currentExternalAssets, uint256 newExternalAssets) external onlyCurator;
```

Three properties of the call:

* **A deviation guard**, time-weighted: the permitted change in basis points grows linearly with the time elapsed since the last update, so a mark cannot jump arbitrarily in one step. The call also passes the current mark and reverts if it has moved since, so an update cannot be applied to a figure the curator did not see.
* **Fees settle first.** Before an overwrite, `setNAV()` settles vault fees against the *old* mark, so investors are not back-charged fees on an increase in NAV that had not yet been reported.
* **Staleness is a signal, not a gate.** Once capital is deployed, `isStale()` reports whether `maxStaleness` has elapsed since the last update. It is informational only — it does not block valuation, `invest()`, or `redeem()`.

```mermaid
sequenceDiagram
    actor Curator
    participant PM as SubaccountPositionManager
    participant VSM as VaultStrategyManager

    Curator->>PM: setNAV(current, new)
    Note right of PM: reverts if current no longer matches<br/>the stored mark, or if the change<br/>exceeds the deviation budget
    PM->>VSM: chargeFees()
    Note right of VSM: fees settle against the old mark
    PM->>PM: externalAssets = new
```

Capital moves only as the vault's native asset; there is no swap router in the path. `invest()` pushes via `safeTransfer`; `redeem()` pulls via `safeTransferFrom`, which requires the subaccount wallet to have approved the PM first.

### VaultFactory

A permissioned factory that deploys `StakedVault` instances and records them in `isFactoryVault`.

The VSM and Redemption Manager are deployed **before** the vault, the reverse of the order the diagram suggests. The caller queries `predictNextVaultAddress()`, constructs the VSM and RM against that predicted address, then passes both into `deployVault`. Vault ownership is set directly in the vault's constructor, so the factory never holds it.

### TimelockGovernable

Not a standalone deployment but the access-control base the vault, the VSM, and every position manager inherit, and the reason two authorities exist throughout the system.

`owner` is instant and handles de-risking and operational calls. `timelock` is delayed and handles risk-increasing ones. The pattern is applied consistently at each layer: changing fee rates on the vault, whitelisting a position manager or raising its cap on the VSM, and adding a subaccount — a new destination for capital — on the position manager.

`setTimelock` is itself `onlyTimelock`, so the timelock authority can only ever be rotated through its own delay. The owner cannot re-point it to bypass that.

### Upgradeability

There is none, by design. No contract in the system uses a proxy, an initializer, or an upgrade hook — no UUPS, no transparent proxy, no `ERC1967`. Deployed bytecode is final.

New behaviour ships as a new deployment rather than an upgrade.

***

Deployed addresses are on [Contract Addresses](/qiro-vaults/technical-overview/contract-addresses.md). Review coverage is on [Security Audits](/qiro-vaults/technical-overview/security-audits.md). Permissions are set out in full on [Protocol Actors](/qiro-vaults/technical-overview/protocol/protocol-actors.md).
