> ## Documentation Index
> Fetch the complete documentation index at: https://continuum-ec12e897.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Keeper

> The off-chain bot that pushes oracle prices, manages the on-chain books, settles committed orders, and routes collateral yield.

The **keeper** is an off-chain Rust async bot that runs Continuum's operational loops:

1. **Oracle updater** - pushes Hermes price + confidence on-chain every \~15 s, and withholds when the underlying market is closed.
2. **CLMM pool manager** - repositions the books to NAV, sizes the spread, refills/trims inventory, and posts the dynamic buffer and vol-informed fees.
3. **Commit settler** - cranks committed-order settlement (which is permissionless - anyone can settle).
4. **Yield router** - moves deployed collateral to the best lending venue and harvests yield.

It is **not in your call path**. User mints, redeems, and trades go directly to on-chain programs; the keeper provisions and prices around them.

## Task inventory

### Oracle updater (\~15 s)

Fetches Pyth Hermes price + confidence per market and pushes both on-chain via `mint_redeem::update_risk_state`. On devnet, Hermes is **the** oracle and every market runs in `ProxyMode` - the on-chain Pyth accounts are not consulted. See [Oracle](/concepts/oracle).

**Closed-market aware:** if the Hermes feed's own `publish_time` is stale (> 120 s, `HERMES_STALE_SECS`), the keeper **withholds** the push entirely. The on-chain TWAP then ages past 300 s, mint/redeem freezes itself, and the books stop repositioning and float at their standing band - off-hours price discovery inside the band, re-anchor to NAV at reopen.

### CLMM pool manager (30 s)

Per market, each tick:

* Feeds the vol state with the latest print (ignoring frozen/repeated closed-hours prices).
* **Repositions** the books when pool price drifts past the per-market gate (QQQ/SPY/XAU 20 bps, VXX 40 bps), posting the vol-scaled shape with protective skew via `set_pool_shape`.
* **Refills and trims bidirectionally**: drained asks restock (sweep bids → `vault_mint_pairs` → fund both sides, delta-neutral); bids cap at \~\$5k/side with excess swept to the vault; asks trim to ≤1.5× bids.
* Posts the **dynamic buffer** target (`set_buffer_target`), the **vol-informed fee**, and the **vol-scaled committed spread** (`set_commit_spread`, floored at 10bps in `settle_swap`) when they move meaningfully.
* Optionally posts the **short-leg volatility decay** (`set_s_gamma_index`, gated by `CLMM_GAMMA_DECAY_ENABLED`, off by default): ratchets the short NAV down by realized per-cycle variance, banking choppiness-convexity as surplus. Ratchet-down only, floored at γ = 0.5. See [NAV → Volatility decay](/concepts/nav#volatility-decay).
* Runs the **cross-market collateral rebalance** (only excess above the source's own dynamic target).

RPC-lean: \~3 reads per market per tick in steady state.

### Commit settler (20 s idle / 5 s while orders pend)

Discovers pending `Order` accounts with one filtered `getProgramAccounts`, provisions inventory (sells first, since they need cUSDC from `vault_redeem_pairs`), and settles up to 10 orders per tick, largest first. Settlement is permissionless - the keeper is a crank, not a gatekeeper.

### Yield router

Routes deployed collateral across Kamino, Save (Solend), and Jupiter Lend (MarginFi is wired but dormant — DefiLlama doesn't index its lending rate) using **30-day-mean** supply APYs from a DefiLlama-backed cache, with switch hysteresis so it doesn't churn between near-equal venues. Harvests land in the treasury vault and reach the collateral ratio via `donate_to_vault`.

With `COLLATERAL_YIELD_RATE_AWARE` (default on), the router uses a **rate-impact "water-fill" allocator**: it models each venue's marginal supply rate as it absorbs a deposit (`a·T/(T+x)`) and splits the deploy so every funded venue lands at the same marginal rate — the allocation that maximizes the blended aggregate yield. Diversification across venues becomes *emergent* (deeper/higher-rate pools take more, overflow spills to the next venue) rather than a fixed per-venue cap.

**Devnet:** a yield emulator mints real cUSDC at the live top-venue APY through the real `harvest_collateral_yield` instruction hourly, so devnet economics mirror mainnet (hard devnet-only gate). Note: the collateral token on devnet is **cUSDC**, and yield deployment is **off by default** (`COLLATERAL_YIELD_ENABLED`).

<Note>
  Remaining gap: the allocator models its own rate impact (above) but does not yet enforce venues' hard deposit caps — a venue at its cap would reject the leg, which the router treats as a failed deploy and retries elsewhere next cycle.
</Note>

### Housekeeping

SOL health (auto-airdrop on devnet), inventory monitor, and a local operator dashboard.

## The spread engine (model-free)

The half-spread that shapes every book is **not a forecasting model** - there is no trained model in the live path. (A linear-quantile forecaster was measured net-negative in research: inside a `max()` with the floors it could only widen, taxing flow without adding protection.) The live spread is a max-of-floors stack:

```
half_spread = max(
    realized-q90 ratchet,    # trailing empirical q90 of window |moves|;
                             # warms within ~8 windows
    floor_mult × σ_60d,      # slow auto-floor from 60-day unconditional vol;
                             # self-corrects mis-set listing floors
    oracle_conf + fixed,     # never quote inside oracle uncertainty
    min_bps,                 # per-market static boot floor:
)                            # QQQ 18, SPY 15, XAU 18, VXX 40
```

Two post-processing steps:

* **Freeze:** above `freeze_trigger`, the book stops repositioning and floats rather than chasing a disorderly tape.
* **Protective drift skew:** the trend-adverse side widens by ×(1 + z), where z is a slow \~1-day return z-score; the other side **never tightens**. Backtested, this fixes trend bleed (e.g. XAU went from −233 to +257 bps/yr) - the classic symmetric inventory skew was falsified.

Vol state seeds at boot from 60 days of Pyth Benchmarks history, so the floors are warm despite restarts; closed-hours frozen prices are ignored.

### Listing without model training

Per-market config (`min_bps` / `floor_mult` / `fee_mult` / window) lives in the keeper config JSON. Listing a new asset needs **no training**: floors plus the ratchet carry both safety and competitiveness. The recipe is steep-wing geometry sized to the asset's vol class, `min_bps` ≥ \~2× its 5-minute σ, and `fee_mult` \~1.0-1.5 for high-vol single names.

## Configuration

All keeper behaviors are env-gated kill switches, **default ON**:

| Switch                     | Gates                                   |
| -------------------------- | --------------------------------------- |
| `CLMM_POOLS_ENABLED`       | Book management as a whole              |
| `CLMM_VOL_SPREAD_ENABLED`  | Vol-scaled spread (off → static floors) |
| `CLMM_BUFFER_POST_ENABLED` | Dynamic buffer posting                  |
| `CLMM_REBALANCE_ENABLED`   | Cross-market collateral rebalance       |
| `CLMM_REFILL_ENABLED`      | Bidirectional refill/trim               |
| `CLMM_FEE_SCALE_ENABLED`   | Vol-informed fee posting                |
| `COMMIT_SETTLE_ENABLED`    | Settlement crank                        |
| `DEVNET_YIELD_EMULATION`   | Devnet yield emulator                   |

Key tunables:

| Variable                      | Default    | Meaning                          |
| ----------------------------- | ---------- | -------------------------------- |
| `CLMM_REPOSITION_BPS[_SYM]`   | per-market | Drift gate before a re-pin       |
| `CLMM_BID_TARGET_USD`         | `5000`     | Bid-side cUSDC cap per side      |
| `CLMM_TRIM_RATIO`             | `1.5`      | Max asks-to-bids value ratio     |
| `COMMIT_SETTLE_INTERVAL_SECS` | `20`       | Settler cadence (idle)           |
| `HERMES_STALE_SECS`           | `120`      | Closed-market withhold threshold |
| `DEVNET_YIELD_APR`            | `0.055`    | Emulator fallback APR            |

Full list: [keeper running guide](/keeper/running).

## Can I run my own keeper?

The reference implementation is public in the [protocol repo](https://github.com/continuum-markets/continuum/tree/main/keeper). What's open vs privileged:

* **Settling committed orders is permissionless.** Anyone can crank `settle_swap` - no authority needed.
* **Book shaping is privileged.** `set_pool_shape`, vault plumbing, `update_risk_state`, and `set_buffer_target` check the keeper/CLP authority. One canonical authority per market; two keepers signing as the same pubkey will collide.
* **Paired arb is open.** Ordinary `mint_paired` / `redeem_paired` plus venue trades need no privilege at all.

## Dashboard

The reference keeper exposes a local HTTP dashboard at `http://localhost:8484` (mainnet) / `8485` (devnet): oracle freshness per market, risk state, book positions and drift, pending committed orders, vault balances, and yield-venue status. This is for the operator - integrators read state directly from on-chain accounts.

## Read more

<CardGroup cols={2}>
  <Card title="Keeper overview" icon="robot" href="/keeper/overview">
    Architecture, loops, and operational detail.
  </Card>

  <Card title="Run a keeper" icon="terminal" href="/keeper/running">
    Config, keypairs, dashboard, troubleshooting.
  </Card>

  <Card title="CLP and the venues" icon="arrows-rotate" href="/concepts/clp">
    The books and vaults these loops manage.
  </Card>

  <Card title="Solvency" icon="shield" href="/concepts/solvency">
    What the protocol enforces independently of the keeper.
  </Card>
</CardGroup>
