> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loopscale.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Earn Vault Architecture

[Earn Vaults](https://docs.loopscale.com/using-loopscale/earn-vaults.md) are curated, multi-venue strategy vaults listed on Loopscale. Loopscale doesn't run its own vault program for them. Each Earn Vault is a vault on an **infrastructure partner's** program, and Loopscale adds discovery, indexing, analytics, and transaction building on top.

The first infrastructure partner is **Exponent**. Every Earn Vault live today is an [Exponent Strategy Vault](https://docs.exponent.finance/developer-strategy-vaults/overview), so this page describes that architecture. Provider-specific details (account layout, governance defaults, withdrawal mechanics) will differ when other providers are added.

## System Overview

An Earn Vault has three layers:

```mermaid theme={null}
flowchart TB
    subgraph L["Loopscale"]
        APP["Loopscale App<br/>Earn page"]
        MKT["Markets API<br/>vault data, positions, history"]
        PAN["Pandora API<br/>transaction builder"]
        IDX["Indexer<br/>vault accounts, requests, events"]
    end

    subgraph E["Exponent Strategy Vault program"]
        V["Vault account<br/>NAV, token entries, positions, config"]
        LP["Share (LP) mint"]
        PR["ExponentPrices<br/>price feeds by price ID"]
        GOV["Proposals and votes"]
    end

    subgraph S["Squads Smart Account"]
        SQ["Squads vault<br/>holds all deployed capital"]
        POL["Policies<br/>whitelisted programs and instructions"]
    end

    subgraph X["Venues"]
        LS["Loopscale"]
        KM["Kamino"]
        EX["Exponent Core / CLMM / Orderbook"]
        OR["Orca, Titan, others"]
    end

    APP --> MKT
    APP --> PAN
    PAN -->|"deposit / withdraw ixs"| V
    IDX -->|"streams updates"| V
    IDX --> MKT
    V --- LP
    V --- PR
    V --- GOV
    V -->|"custody"| SQ
    SQ -->|"manager actions, gated by"| POL
    POL --> LS
    POL --> KM
    POL --> EX
    POL --> OR
```

* **Loopscale layer.** Lists vaults, indexes onchain state into snapshots and per-wallet positions, computes APY and allocation, and builds deposit and withdrawal transactions.
* **Vault program** (`sVau1tXvayVWfotzm9Ahcv2qfnnfRWttt78BCnNC6dD`). Tracks NAV, mints and burns shares, processes the withdrawal queue, runs governance, and validates every manager action against vault-level invariants.
* **Custody.** Capital sits in a [Squads](https://squads.so) Smart Account that the vault controls. The manager never holds depositor funds directly. Every strategy action is a Squads transaction that must match a pre-approved **policy**.

## Onchain Accounts

**Vault**

The core account. Stores the underlying mint, the share (LP) mint, the Squads addresses, the list of **token entries**, the list of **strategy positions**, role assignments, fee and withdrawal configuration, governance configuration, and the vault's financials (idle AUM, deployed AUM, LP supply, pending withdrawals, instant-withdrawal window state). Derived from `["vault", seed_id]`.

**Share (LP) mint**

SPL token representing proportional ownership of the vault. Minted on deposit and burned on withdrawal at the current NAV per share. Queued and vote-staked shares sit in the vault's LP escrow account.

**Token entries**

Each token the vault can hold (its underlying plus any other accepted tokens) has an entry with a mint, a **price ID** into `ExponentPrices`, a token account owned by the Squads vault (where deposits land and capital is deployed from), and a token account owned by the vault (where withdrawal fills are staged). Deposits and withdrawals can only use tokens that have an entry.

**Strategy positions**

Every non-token position the vault tracks for NAV: Loopscale loans, strategies, and vault stakes; Kamino obligations and farms; Exponent yield, CLMM, and orderbook positions; Orca Whirlpool positions; and tracked token accounts such as PTs or tranche tokens. A position must be registered before the vault can value it, and the vault won't let the manager trade into an unregistered one.

**ExponentPrices**

A global price account. Each price ID resolves to a feed (Pyth, Scope, Chainlink, stake-pool rate, PT/YT pricing, TWAP, Kamino vault share price, Loopscale vault LP price, and others), or to a product of feeds, e.g. `JitoSOL/SOL × SOL/USD`. All values are converted into the vault's underlying.

**Withdrawal account**

One account per queued withdrawal request. Records the owner, the shares requested and still remaining, and the fills made against it (which token entry, what share of the request). Its address is the `requestId` returned by the Loopscale API.

**Action proposal / vote account**

Governance state for a proposed change, plus one vote account per LP holder voting on it. See [Governance](#governance).

## Roles

Each role holds one or more keys.

| Role | Responsibility |
| - | - |
| **Manager** | Vault admin. Manages roles, settings, and policies (through governance once the vault holds AUM), fills queued withdrawals, collects the management fee. |
| **Curator** | Configures what the vault can hold and do: token entries, supported assets, and policy parameters. |
| **Allocator** | Runs the strategy day to day: sends Squads-wrapped instructions (lend, borrow, swap, LP, buy PTs) within the approved policies. |
| **Sentinel** | Risk backstop, typically operated by Exponent. Can block withdrawals, manager actions, or user actions with vault flags, cancel proposals, and clear the NAV circuit breaker. |

On Loopscale-curated vaults the **Overview** tab shows the curator, the strategist (allocator), the sentinel, and the Squads vault address.

## How Manager Actions Are Constrained

Manager discretion is limited by two independent checks on every strategy transaction.

**1. Squads policies (what can be called).** A `ProgramInteraction` policy whitelists a target program, the instructions allowed on it, and constraints on accounts and instruction data, e.g. only these Kamino markets, only this Loopscale strategy, only senior-tranche deposits. Squads rejects any instruction that doesn't match an approved policy.

**2. The vault's post-hook (what the result is allowed to be).** Every policy attaches a post-hook that calls the vault program's `validate_interaction_hook` after the instruction runs. The hook sees exactly which instructions executed and re-checks the vault's invariants:

| Invariant | Enforced by | Error |
| - | - | - |
| Only supported instructions, and only into registered positions | Hook instruction coverage | `UnsupportedInstruction` (6094), `MissingPositionForTrade` (6107) |
| Idle reserves stay at or above `reserves_share_bp` of AUM | Reserves floor | `ReservesExceeded` (6106) |
| Swaps, PT/YT trades, and Loopscale vault deposits lose no more than `max_slippage_bp` of value vs. oracle | Slippage check (the action must be alone in its transaction) | `SlippageExceeded` (6120), `SlippageInteractionNotIsolated` (6119) |
| NAV per share can't move more than `nav_aum_change_threshold_bps` within a window | NAV circuit breaker | `NavAumChangeExceeded` (6125), `NavAumCircuitBreakerActive` (6124) |
| Token balances and LP supply change only in ways the instruction explains | Balance reconciliation | `TokenBalanceChangeMismatch` (6091), `UnexpectedLpSupplyChange` (6093) |
| Queued withdrawals are filled before their window closes | Backlog check, blocks new actions until cleared | `PendingWithdrawalsPastDue` (6059) |

Policies can only be added or changed directly while the vault is empty. Once it holds depositor capital, policy changes must go through governance.

## NAV, Pricing, and AUM Refresh

```
Total AUM      = idle reserves (aum_in_base) + deployed positions (aum_in_base_in_positions)
NAV per share  = Total AUM / effective LP supply
```

Effective LP supply includes management-fee shares that have accrued but not yet been minted, so NAV already nets out the fee.

The vault stores AUM onchain and recomputes it with the permissionless `refresh_aum` instruction. That instruction walks every token entry and strategy position and values each through `ExponentPrices`. Price checks are **same-slot strict**: each price, and each Kamino reserve the vault touches, must have been updated in the slot where it's read, otherwise the program throws `StalePriceData` (6080). Any instruction that changes vault configuration or positions also sets an `AUM_REFRESH_REQUIRED` flag. Until a fresh refresh clears it, deposits and withdrawals fail with `AumRefreshRequired` (6123).

That's why every Earn transaction from the Loopscale API comes as **two transactions sent as one Jito bundle**:

1. **AUM refresh.** Price updates, Kamino/Scope reserve refreshes, then `refresh_aum`.
2. **Action.** The deposit, withdrawal, claim, or cancel, plus the Jito tip.

The bundle makes both land in the same slot, so the action is priced at the NAV computed one instruction earlier. Splitting them into two normal sends can leave the action priced against stale data, or failing outright.

## Deposits

`deposit_liquidity(amount, min_lp_out)` transfers a token-entry token from the user into the Squads vault and mints shares:

```
shares out = amount (in underlying terms) × LP supply / Total AUM
```

The deposit fails if fewer than `min_lp_out` shares would be minted (`MinLpOutNotMet`, 6016) or if it would push the vault past its deposit cap, `max_aum_supply` (`LpSupplyMaximumExceeded`, 6015).

## Withdrawals

Withdrawals take one of two paths, depending on the vault's configuration and current liquidity.

> [tab] Queued
>
> 1. **Request.** `queue_withdrawal` moves the user's shares into escrow and creates a withdrawal account. No funds move yet.
> 2. **Fill.** The manager unwinds positions as needed and calls `fill_withdrawal`, through a Squads policy, choosing which token entries pay out and how much of the request each covers. A request can be filled in several partial fills. The standard withdrawal fee (`normal_withdrawal_cut_bp`) applies.
> 3. **Claim.** Once the request is filled and the vault's withdrawal period has unlocked, the user calls `execute_withdrawal` to receive the tokens.
>
> Until the request is filled, the user can **cancel** it to get their shares back, once a minimum cooldown (`withdrawal_cancel_cooldown_seconds`) has passed. The payout token is the manager's choice at fill time. A preference passed through the Loopscale API is recorded and honored where possible, but isn't enforced onchain.

> [tab] Instant
>
> `execute_withdrawal_from_reserves` burns shares and pays out from idle reserves in one step. It charges the standard fee plus `fast_withdrawal_cut_bp`, which stays in the vault for remaining depositors.
>
> Instant capacity is the smaller of:
>
> * the reserve buffer (`reserves_share_bp` × AUM) minus the value of shares already queued, and
> * the per-window instant cap (`instant_withdrawal_window_limit_bp` × AUM, snapshotted at the window start) minus what's already been withdrawn instantly this window,
>
> further limited by the balance of the chosen payout token. Capacity drops to zero while withdrawals are blocked or the NAV circuit breaker is active.

### Withdrawal period

Each vault sets a withdrawal period that controls when queued requests can be claimed and when the instant-withdrawal window resets:

* **Interval**: a fixed lock time after the request (between 12 hours and 30 days).
* **Weekly schedule**: specific UTC weekdays, optionally every Nth week. For example, a vault might process withdrawals on Tuesdays and Fridays.

The manager must fill every queued request before its window deadline. If they don't, the vault blocks new manager actions until the backlog is cleared, so a stalled queue can't sit while the strategy keeps trading.

## Fees

| Fee | Charged on | Goes to |
| - | - | - |
| **Management fee** | AUM, accrued per second as unminted shares | Fee treasury, when collected |
| **Withdrawal fee** (`normal_withdrawal_cut_bp`) | Queued withdrawals at fill | Fee treasury |
| **Instant withdrawal fee** (`fast_withdrawal_cut_bp`, added to the withdrawal fee) | Instant withdrawals | Retained in the vault for remaining depositors |

There is no performance fee. All fee parameters can only change through governance once the vault holds AUM.

> [!NOTE]
>
> Raw values in the vault account use different precisions: `management_fee_bps` is in millionths (`1,000,000` = 100%/yr, so `3000` = 0.30%), while the withdrawal cuts use standard basis points (`10,000` = 100%). The Loopscale app converts these for display.

## Governance

Once a vault holds depositor capital, the manager can only change settings, roles, fees, deposit caps, positions, or policies through an onchain **proposal**:

```mermaid theme={null}
flowchart LR
    D["Draft<br/>manager builds the<br/>proposal's actions"] --> A["Active<br/>voting period"]
    A -->|"rejections below threshold"| AP["Approved"]
    A -->|"rejections reach threshold"| R["Rejected"]
    AP -->|"timelock elapses"| EX["Executed"]
    D -.->|"manager or sentinel"| C["Cancelled"]
    A -.-> C
    AP -.-> C
```

* **Rejection-based voting.** Proposals pass by default. Share holders can stake shares to vote **Reject**, or **Opt Out**, which queues their shares for withdrawal if the proposal passes. If rejections reach the vault's rejection threshold (a share of LP supply snapshotted when voting starts), the proposal fails.
* **Timelock.** An approved proposal can only execute after the timelock, so depositors always get a window to exit before a material change takes effect.
* **Defaults.** Exponent's defaults are a 48-hour voting period (configurable from 1 hour to 7 days), a 24-hour timelock, and a 33% rejection threshold. Each vault can set its own, and the live values appear under **Governance** on the vault's Overview tab.
* Finalizing and executing a proposal are permissionless. Anyone can call them once the period has passed.

## Supported Venues

Policies can whitelist any of the integrations the vault program knows how to value:

| Venue | Typical actions |
| - | - |
| **Loopscale** | Lend through strategies, borrow and loop against collateral, refinance, deposit into Loopscale Vaults and stake LP |
| **Kamino** | Lend and borrow, Kamino vaults, farms |
| **Exponent Core** | Mint and redeem SY, strip and merge PT/YT |
| **Exponent markets** | PT/YT trading on the AMM and CLMM, CLMM liquidity, orderbook offers |
| **Exponent Tranching** | Senior and junior tranche deposits and withdrawals |
| **Titan** | Aggregated swaps |
| **Orca** | Whirlpool positions and swaps |
| **Jupiter Lend** | Earn and borrow |

## The Loopscale Layer

Loopscale reads everything from chain and adds no custody or onchain permissions of its own.

* **Registry.** Each listed vault is registered with its provider, share mint, accepted asset, display metadata, and a status: `Draft` (shown, deposits paused), `Active`, `Paused`, or `Deprecated`. Flags record which withdrawal paths the vault offers and whether depositors must accept terms first.
* **Indexing.** Loopscale streams the vault program's accounts and transactions. It keeps a decoded copy of each vault account, tracks every withdrawal request and its fills, and writes a per-wallet event ledger (deposit, instant withdraw, request, cancel, fill, claim) used for cost basis and PnL.
* **Snapshots.** Every 15 minutes, Loopscale snapshots share supply, NAV per share, AUM, pending withdrawals, and utilization (deployed AUM ÷ total AUM).
* **Yield figures.**
  * *APY* (`displayApyBps`) is forward-looking: each position's current rate (lending, borrowing cost, PT yield, LP fees) weighted by its value, netted across lend and borrow legs.
  * *Realized return* (`realizedReturnBps24h/7d/30d`) is backward-looking: the simple annualized change in NAV per share over the window.
  * *Rewards APY* annualizes active reward emissions against AUM.
  * *Headline APY* (`headlineApyBps`) is the APY, or the 7-day realized return when no APY is available, plus rewards APY.
* **Allocation.** Each refresh splits AUM into unallocated liquidity (idle token-entry balances) and deployed positions grouped by venue, each with its share of AUM and APY. This feeds the **Allocation** tab.
* **Transactions.** The Pandora API wraps the provider's SDK to build deposit, withdraw, claim, and cancel transactions, prepends the AUM-refresh transaction, and attaches the address lookup tables. See [Integrate Earn Vaults](https://docs.loopscale.com/api-reference/guides/earn-integration.md).

## Security

The Exponent vault program has been audited by Adevar Labs, OtterSec, and Certora, and Squads Smart Accounts by OtterSec, Offside Labs, and Certora. Reports are published in [Exponent's audit repository](https://github.com/exponent-finance/exponent-audits). See [Audits](https://docs.loopscale.com/resources/audits.md) for Loopscale's own audits, and [Earn Vault risks](https://docs.loopscale.com/using-loopscale/earn-vaults.md#risks) for what remains.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.