Skip to main content

Documentation Index

Fetch the complete documentation index at: https://continuum-ec12e897.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

The keeper is an off-chain Rust async bot that owns three operational duties on Continuum:
  1. Oracle updates - publishes Pyth/Hermes observations to the oracle program every ~15 s.
  2. Peg-enforcing arbitrage - closes price divergence between Meteora DLMM pools and protocol NAV.
  3. DLMM position management - opens, rebalances, and closes the protocol’s LP positions on Meteora.
It is not in your call path. User mints and redeems go directly to on-chain programs. The keeper acts independently to keep prices anchored.

Privileges

The keeper signs as the privileged keeper_authority stored on each market account. This grants it access to:
  • keeper_mint_single(is_long, collateral) - fee-free, single-sided mint at NAV.
  • keeper_redeem_single(is_long, token_amount) - fee-free, single-sided redeem at NAV.
  • update_risk_state(new_state) - mirror oracle risk state into the market.
  • All clp_* proxy instructions - Meteora position management on the CLP PDA’s behalf.
The single-sided privileges would be unsafe for an arbitrary user (mint without an offsetting deposit creates an unbacked claim against the vault). The keeper uses them only inside two-phase arb cycles that net back to cUSDC. See Solvency invariants.

Architecture

┌─────────────────────────── keeper bot ────────────────────────────┐
│                                                                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌────────────────┐   │
│  │ Oracle   │  │ Risk     │  │ Inventory│  │ DLMM rebalancer│   │
│  │ 15s      │  │ 15s      │  │ 60s      │  │ 120s           │   │
│  └──────────┘  └──────────┘  └──────────┘  └────────────────┘   │
│                                                                    │
│  ┌──────────┐  ┌────────────────┐  ┌──────────────────────────┐ │
│  │ Arb scan │  │ Liquidity      │  │ Peer heartbeat (multi-   │ │
│  │ 15s      │  │ seeder 600s    │  │ keeper coordination) 30s │ │
│  └──────────┘  └────────────────┘  └──────────────────────────┘ │
│                                                                    │
│  Signers: payer (KEEPER_KEYPAIR) + CLP authority (CLP_AUTHORITY_KEYPAIR) │
│  RPC: primary + fallback (auto-rotates on rate-limit)             │
│  Dashboard: localhost:8484 (mainnet), 8485 (devnet)               │
└────────────────────────────────────────────────────────────────────┘
Each loop is independent. They share state through an Arc<RwLock<KeeperState>>. Failure of one loop doesn’t crash the others.

What each loop does

Oracle (15s)

Fetches Pyth Hermes for each active market, picks the session-aware feed (regular / pre-market / post-market / overnight), and calls oracle::update_price_observation on-chain.

Risk monitor (15s)

Reads each market’s OracleConfig, derives the right risk state (Normal / ProxyMode / Stress / Recovery), and calls mint_redeem::update_risk_state if it differs from the current on-chain value.

Inventory (60s)

Computes per-market q-imbalance (long supply vs short supply weighted by NAV) and drawdown vs peak. Tracks soft and hard bounds. Drives downstream gating in arb (won’t push past hard bounds even if profitable).

DLMM rebalancer (120s)

For each market × side, decides whether to remove + close + reopen the Meteora position. Signals to rebalance:
  • Active bin has overflowed the position’s range (bin price moved past the edge).
  • NAV target has moved enough that the existing range no longer covers [active, target].
  • Volatility regime has shifted (calm ↔ stressed) - wider/narrower bin radius.
The rebalancer skips the rebuild if the existing range still covers [min(active, target), max(active, target)] with a small slack. Saves ~0.05 SOL of rent + several CU-heavy txs per skipped cycle. Full DLMM details

Arb scanner (15s)

Two paths, evaluated each tick per market × side: Single-side (primary): pool diverges from per-side NAV by more than pool_fee_bps + slip/2.
Pool over NAV  → keeper_mint_single + sell on pool
Pool under NAV → buy on pool + keeper_redeem_single
Paired: combined L+S spread diverges from combined NAV.
combined_dex > combined_nav  → mint_paired + sell both legs
combined_dex < combined_nav  → buy both legs + redeem_paired
Each cycle ends in cUSDC at the keeper or CLP vault. The end-of-cycle asymmetric residual sweep redeems any leftover synth (rounding dust) via either redeem_paired if both sides have stock, or keeper_redeem_single for one-sided residue. Full arb path details

Liquidity seeder (600s)

Allocates capital from GlobalClp (the global cUSDC vault) into per-market vaults. Edge-weighted: each market gets a weight blending its OI-cap share with its observed (fee_rate + arb_rate) / (risk_q_deviation + drawdown) edge. Recalls from worst-edge markets when global vault free balance drops below a threshold.

Peer heartbeat (30s)

Multi-keeper primary/standby coordination. Currently basic - one canonical keeper at a time. Future versions will support active/active across multiple operators.

Solvency guarantees

Every operational invariant in the keeper is designed so that a crash mid-arb leaves the protocol in a recoverable state.
  1. Boot-time sweep. First action after startup: read keeper wallet’s L and S balances. Both sides → redeem_paired. One side → keeper_redeem_single. Reclaims any leftover synth from prior crashes.
  2. End-of-cycle sweep. After every arb (single-side or paired), measure post-swap L/S balances and redeem residue. Built at execution time so the slippage-buffer micro-leak is recovered, not accumulated.
  3. Profit deposit gating. deposit_profit to the global vault sends min(actual_delta, expected_profit). A favorable-slippage run doesn’t over-deposit; an unprofitable arb deposits nothing.
  4. Dynamic trade sizing. Per scan, trade_amount = min(pool_depth, 90% × keeper_balance) so the keeper never bites off more than its wallet can chew.

Configuration

The reference keeper is configured via .env. Key knobs:
VariableDefaultMeaning
MAINNET_RPC_URL / DEVNET_RPC_URLofficial clusterPrimary RPC
KEEPER_KEYPAIR~/.config/solana/id.jsonPayer + arb signer
CLP_AUTHORITY_KEYPAIRkeeper/key/keeper-keypair.jsonCLP proxy signer
ARB_INTERVAL_SECS15Arb scan cadence
DLMM_INTERVAL_SECS120DLMM rebalance cadence
MIN_ARB_PROFIT100000 (= $0.10)Skip arbs below this
MAX_SLIPPAGE_BPS50Profit buffer
LIQUIDITY_SEED_FRACTION_BPS6500Fraction of global vault per seed cycle
ACTIVE_MARKETSQQQComma-separated symbols
Full list: keeper running guide.

Can I run my own keeper?

Yes - and there’s a public reference implementation in the protocol repo. Limitations to know:
  • Only one canonical keeper authority per market. The market’s keeper_authority field is single-pubkey today. Two keepers signing as the same pubkey will collide. Multi-keeper requires either coordination (peer heartbeat with primary/standby logic) or future on-chain support for keeper sets.
  • You don’t need keeper authority to arb. Single-sided keeper-only paths are fee-free; paired arb is open to anyone. An external arb bot can run paired-arb without any privilege at all - just ordinary mint_paired + redeem_paired + Meteora swaps.
  • The CLP authority is separate from the payer. The keeper signs CLP instructions with CLP_AUTHORITY_KEYPAIR, distinct from the payer keypair. This lets operators rotate signers without re-creating positions.
Running a keeper · External arb bot

Dashboard

The reference keeper exposes a local HTTP dashboard at http://localhost:8484 (mainnet) / 8485 (devnet). It shows:
  • Oracle state and last-update timestamps per market.
  • Risk state per market.
  • Outstanding arb opportunities (size, direction, expected profit).
  • Position status per Meteora pool (active bin, range, allocated capital).
  • Vault balances (global, per-market).
This is for the operator. As an integrator, you read state directly from on-chain accounts.

Read more

Arb paths in detail

The four arb cycles, sizing logic, profit accounting.

DLMM position management

Vol-adaptive radius, fee-weighted centering, skip-rebuild optimization.

Run a keeper

Config, keypairs, dashboard, troubleshooting.

Solvency invariants

What the protocol enforces no matter what the keeper does.