> For the complete documentation index, see [llms.txt](https://docs.keystonefi.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.keystonefi.xyz/start-here/fees.md).

# What it costs

{% hint style="info" %}
**TL;DR** 0% management fee. 20% performance fee, charged only above a high-water mark *and* only above a hurdle set at the USDC lending rate — so the vault has to beat a Kamino deposit before it charges anything. The rest of this page is why the hurdle exists and how the high-water mark behaves.
{% endhint %}

**The floor is a Kamino deposit; the upside is 80% of everything above it.** The performance fee applies only above a benchmark set to the USDC lending rate, so a year spent merely matching what the same dollars would have earned lending costs nothing — the fee cannot put a holder behind the benchmark — and above it they keep 80 cents of every dollar of edge.

There's one fee schedule, and it applies to the vault as a whole rather than to individual holders.

Fees are paid in USDC out of the vault. They are not paid by minting new tokens, and nothing is ever taken out of your balance. What you see afterward is a share price that reflects the money that actually left.

***

## Rates

The whole schedule, before any of the reasoning below:

| Fee                     | Rate                                                                      | Mechanism                                                                                                                                                                                |
| ----------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Management              | **0%**                                                                    | Not charged. Capital does not pay rent.                                                                                                                                                  |
| Performance             | **20% above the HWM, itself above a hurdle set to the USDC lending rate** | Accrues only on gains that beat both the high-water mark and the benchmark, and is set aside as it's earned. The hurdle is `hurdle_apr_bps`, measured live at deploy — not a fixed rate. |
| Rebalance / mode switch | **0%**                                                                    | No fee charged on internal position rotation.                                                                                                                                            |
| Withdrawal              | **0%**                                                                    | Neither instant nor queued path charges a fee.                                                                                                                                           |

***

## High-water mark (HWM)

The high-water mark is the highest **gross** share price the vault has ever reached, stored as `vault.hwm_share_price_1e9`. Performance fees accrue only while the gross price is above it.

The practical effect: if the vault dips and then recovers, you pay nothing on the recovery. You're only charged on genuinely new gains.

### The hurdle: the HWM ratchets at the benchmark rate

A plain high-water mark charges a fifth of *every* dollar of gain, including gains the vault didn't really add. The vault spends much of its life Parked, which is USDC lent on Kamino — something any holder can do directly in one click. Charging a performance fee on that leaves the holder behind where they'd have been without the vault at all, which is not a fee schedule anyone should defend.

So the HWM advances *on its own* at `hurdle_apr_bps` per year, whether or not the vault earns anything:

```
dt      = now − last_fee_accrual_ts
hwm    += hwm × hurdle_apr_bps × dt / (10_000 × SECONDS_PER_YEAR)
```

Only the excess over that ratchet is fee-bearing. A year that returns exactly the hurdle rate costs nothing; a year above it is charged only on the part above.

**`hurdle_apr_bps` is not a compiled-in rate.** the bootstrap script reads the live Kamino USDC supply APY at deploy and writes that value. On mainnet a failed read aborts the deploy rather than falling back, because the fallback would be exactly the stale constant the hurdle exists to avoid. (The default in `InitializeParams` is a tests-and-localnet value, not the launch one.) After deploy it's adjustable via `update_params`, capped at 20%/yr, so it can track the benchmark as that moves. Setting it to `0` restores the plain HWM exactly.

The drift is not hypothetical — the rate moved by most of a point within days of the value first being chosen. Any rate printed in these docs is a dated snapshot; the live one is whatever `hurdle_apr_bps` currently reads on the vault account.

### What the hurdle is worth

Strategy, gross return and drawdown are unchanged by it; the fee schedule is the only thing that moved. Over the [backtest window](/how-it-works/strategy-and-modes/historical-simulation.md) the hurdle lifts net APY from **4.27% to 5.04%**, because fees charged fall from $0.83 to $0.25 per $100 — a 70% cut. Parked, you keep the lending rate. Hedged, the edge over lending it yourself is about 135 bps.

```bash
npm run fund:backtest                    # with the hurdle (current)
HURDLE_APR_BPS=0 npm run fund:backtest   # the old flat-fee model
```

### What happens when lending spikes

Measuring the rate at deploy fixes the starting value, not the drift. USDC lending goes rich during liquidation cascades, and nothing updates the stored number on its own. That cuts both ways.

**A stale hurdle is worse than no hurdle.** If lending runs at triple the stored rate, the vault charges a performance fee on return the holder could have had by lending directly — and the overcharge scales with the spike. The hurdle has to be maintained; it is not set-and-forget.

**But chasing the spike is also wrong**, because the ratchet is one-way. Raise the hurdle, leave it a month, revert — the benchmark has already advanced and there is no un-ratchet, so a transient spike would suppress fees permanently.

Two mechanisms resolve that:

* **On-chain, the gap is capped.** The ratchet stops once the HWM sits `MAX_HURDLE_GAP_BPS` (5%) above the current gross share price. Over-reacting to a spike therefore costs a bounded amount rather than an unbounded one, which makes tracking the benchmark honestly a safe thing to do. It also stops a long flat stretch from putting the HWM so far overhead that every later recovery is fee-free forever.
* **Off-chain, the keeper watches the drift.** The `watchdog` duty compares `hurdle_apr_bps` against the *smoothed* lending APY rather than the spot rate, and warns once they diverge. Smoothing is the point: it fires on a rate that has actually moved, not on the first hour of a cascade.

**The operator rule:** raise the hurdle when the smoothed benchmark has held above it for a sustained stretch, not on a single spike. The cap means being late is cheap and being early is bounded.

There's a second-order effect too. A lending spike raises the opportunity cost of being hedged, so the keeper's dynamic threshold will already be pushing the vault toward Parked. The hurdle question and the regime question move together.

The implementation handles a few edge cases deliberately:

* **An empty vault does not ratchet.** A vault at zero shares for a year would otherwise hand the first depositor a hurdle debt to climb out of.
* **The ratchet won't run over intervals under an hour.** It rounds to whole units, so a short interval throws away most of its own increment — and because `settle` is permissionless, a fast crank could otherwise have quietly shrunk the hurdle. Short intervals are banked until an hour has accumulated, so the hurdle comes out the same however often anyone cranks.
* **A zero `last_fee_accrual_ts` does not ratchet.** The field is zero on a vault whose state predates it; without the guard `dt` would be the entire Unix epoch and the hurdle would swallow every fee the vault ever earns. The first accrual just sets the clock.

### Fees accrue continuously, not at collection

Fees are set aside as they're earned. `accrue_perf_fees` runs on every price change (`settle`, `attest_nav`) and again before any deposit mints shares:

```
gross_price      = gross_effective_nav_usdc × 1e9 / total_shares
if gross_price <= hwm_share_price_1e9:  nothing accrues
gain_per_share   = gross_price − hwm_share_price_1e9
fee              = gain_per_share × total_shares × perf_fee_bps / 1e13   // 1e9 price × 1e4 bps
pending_perf_fees_usdc += fee        (capped at gross_effective_nav_usdc)
hwm_share_price_1e9     = gross_price
```

Both of the consequences below were audit fixes:

* **`pending_perf_fees_usdc` is subtracted from `effective_nav_usdc`.** So the share price is always already net of fees owed, and collecting them later doesn't step the price down. An exiting holder also can't dodge a fee they've already accrued.
* **The HWM advances before new shares are minted.** A fresh depositor is never charged on appreciation that happened before they arrived. Accruing incrementally is what avoids that: recomputing `(price − HWM) × total_shares` once at collection time would apply a stale HWM to a grown share count and overcharge anyone who entered above it.

### What `collect_fees` does

By the time it runs, the amount is already determined. Collection just moves money.

1. Requires `position_mode == Idle` (Parked) and `!paused`, and only the admin can call it.
2. Calls `accrue_perf_fees` to catch up to the current price, then reads `pending_perf_fees_usdc`. If it's zero, the call is a no-op.
3. Requires the vault's main USDC account to hold at least the full fee, or reverts with `InsufficientLiquidityBuffer`.
4. Transfers the fee to the admin's USDC account and reduces `cached_nav_usdc` by the same amount.
5. Zeroes `pending_perf_fees_usdc` and lowers the HWM by exactly the per-share amount paid out (`perf_fee_usdc × 1e9 / total_shares`), updating the peak if the new price is a high.

   **Why "lower by the payout" and not "re-anchor to the current price".** Those agree when you collect at a high, but accrued fees survive a dip — collection is the only thing that zeroes them. Re-anchoring while the price was below the HWM would drop the bar to the bottom of the dip and charge the whole recovery a second time, breaking the guarantee above. Subtracting only the payout keeps the gap intact, so a recovery over ground you have already paid for stays free, and the hurdle's accumulated benchmark survives too.

Because step 5 removes exactly what step 4 accounted for, collection is **share-price-neutral**.

***

## No reserve fund

There is no reserve or insurance fund, and that is a design choice rather than a gap to close later.

* [**ksUSD defends no peg**](/start-here/reserve-asset.md)**.** An insurance fund is what a pegged dollar needs, because a discount there is an existential failure. Here the share price is the shock absorber: NAV is marked at market, the price is designed to be able to fall, and a holder exiting into a bad unwind bears it rather than passing it to the holders who stayed. Reserving against market risk would contradict all of that.
* **The arithmetic could not work anyway.** A 1% jitoSOL discount costs **0.82% of NAV**. No skim on a 20% performance fee gets near that, and a reserve too small to cover a single incident is machinery without a purpose — every instruction and state field it needs is audit surface.

**What Keystone does stand behind** is narrower: shortfalls the *protocol* caused — a bad fill, an oracle mismark later corrected, an accounting gap — cases where the vault did something to a holder rather than the market doing it to everyone. The operator makes those whole from its own fees by sending USDC to the vault's main account; `settle` counts idle USDC into NAV, so the share price reflects it at the next crank. No instruction is needed and no capital sits idle against an event that has not happened.

***

## Who can call `collect_fees`

* Only the admin can call `collect_fees`
* The math is fully on-chain and reproducible from `cached_nav_usdc`, `queue_pending_usdc`, `pending_perf_fees_usdc`, `total_shares`, and `hwm_share_price_1e9`
* `pending_perf_fees_usdc` is readable at any time, so the fee owed is visible before anyone collects it
* Anyone can check the fee delta before and after. Calling it needs the admin; auditing it needs nobody.

***

## Worked example

**Start:**

* Vault holds $1,000,000 NAV, 1,000,000 ksUSD outstanding
* Share price = $1.0000 · HWM = $1.0000

**Carry accrues** over a quarter. `cached_nav_usdc` grows to $1,030,000, and each `settle` along the way calls `accrue_perf_fees`. By the end:

* Gross share price: $1.0300, so gross gain per share is $0.0300
* Accrued fee: 0.0300 × 1,000,000 × 0.20 = **$6,000**, held in `pending_perf_fees_usdc`
* HWM has advanced to **$1.0300** (gross)
* Net share price: ($1,030,000 − $6,000) / 1,000,000 = **$1.0240**

Holders have been seeing $1.0240 all along, because the accrued fee was already carved out. Now the admin runs `close_position` to move the vault to Parked, then calls `collect_fees`:

* **$6,000** → admin's USDC account
* `cached_nav_usdc`: $1,030,000 − $6,000 = **$1,024,000**
* `pending_perf_fees_usdc` → 0
* Net share price: $1,024,000 / 1,000,000 = **$1.0240**, unchanged
* HWM re-anchors to **$1.0240**

The share price doesn't move at collection, which is the whole design goal. The next fee only accrues above $1.0240, so recovering from a drawdown is free.

***

## Related

* [NAV & share pricing](/how-it-works/strategy-and-modes/nav-calculation.md) — share-price math, `effective_nav_usdc`
* [Admin operations](/run-it/keeper-bot/admin-ops.md) — `collect_fees`
* [Security model](/reference/security.md) — admin role and HWM monotonicity guarantee
* [Whitepaper — Yield sources & fees](/how-it-works/whitepaper.md#iv-the-share-and-what-it-costs)
