Skip to main content
The keeper’s source and binary are not published. This page documents how the protocol’s keeper is configured: what it needs, the environment variables it reads and their defaults, its logs, and how it fails. Integrators do not need any of it; mint, redeem and trades go straight to the programs.

What it needs

  • Two funded keypairs. The keeper wallet must be market.keeper_authority on every market it operates, and pays for price pushes and settlements. The CLP authority signs book shapes and inventory moves and pays rent for new pool accounts. On devnet it is also each market’s admin authority, which fee posting needs.
  • RPC endpoints. Market discovery uses getAccountInfo. The commit settler uses a filtered getProgramAccounts and websocket subscriptions, which some paid RPC tiers disable, so at least one endpoint must allow them.
  • The mint-redeem program ID. Without DEVNET_MINT_REDEEM_PROGRAM_ID or MINT_REDEEM_PROGRAM_ID, market discovery fails and the keeper runs QQQ only, with a warning.
  • Storage for the transaction journal. Every transaction that moves funds is journaled first, in Cloudflare R2 or under a persistent DATA_DIR. Without a working journal the keeper disables those transactions; price pushes still go out.
The protocol deploys the keeper as a Docker image built with Rust 1.88. The image sets RUST_LOG=info and UPDATE_INTERVAL_MS=5000 and exposes port 8484. The process exits with a non-zero code as soon as any of its loops ends, so it runs under a supervisor that restarts it on failure.

Environment variables

The keeper loads .env from its working directory, or the file named by KEEPER_ENV_FILE (which must come from the process environment). Per-network settings, such as the RPC URL, collateral mint and program IDs, are read with a DEVNET_ or MAINNET_ prefix first, then without it.

Networks and keys

On devnet, an unset USDC_MINT falls back to mainnet USDC. Every token-account derivation is then wrong, vault balances read as 0, and loops skip markets without an error. Always set DEVNET_USDC_MINT or USDC_MINT to cUSDC on devnet.

Price source

A print more than 10% from the last accepted price is held, and an operator releases it by writing an R2 object, as described in CNBC price holds. The updater also pushes nothing for a market after its scheduled close plus 15 minutes. When the fallback source prices a market, the confidence band comes from a GJR-GARCH volatility model; with Pyth, the keeper uses the wider of Pyth’s band and the model’s. Mainnet will price from Pyth (Pyth Pro).

Book manager and parameter posting

Off-hours book

Committed orders

Liquidity reallocator

Treasury withdrawal rate

Imbalance control

Short rebate

Redemption settler

Devnet treasury trim

Health and alerts

Storage, dashboard and logs

Collateral yield

The allocator’s limits come from the stress assumptions below and the treasury balance, which it reads on chain, rather than from fixed percentages. See Keeper → Yield allocator.

Off by default

Primary and standby

Several instances can run with the same keys. Set KEEPER_PEERS on each to the dashboard URLs of the others. Every 30 s each instance probes /api/status/<network> on its peers, and the one with the oldest start time is primary. Standby instances still push prices, which is harmless to repeat, but skip the loops that move funds, such as the book manager and the commit settler. When the primary stops answering, the oldest remaining instance takes over at the next probe. Each market has one keeper_authority; the market admin can rotate it with update_keeper_authority to split markets between operators. Bots without either key can run alongside: cancel_swap and claim are permissionless, and anyone can trade the books. settle_swap needs clp.authority or market.keeper_authority.

Logging

The binary crate is named keeper, so log targets are its module paths: keeper::oracle, keeper::clmm_pool, keeper::commit_settler, keeper::sol_health, keeper::tx, keeper::rpc. A filter for continuum_keeper matches nothing and silences the keeper.
keeper::clmm_pool=debug logs every reposition, refill and posting decision, and keeper::commit_settler=debug logs committed-order discovery and settlement. keeper::rpc=warn keeps only RPC failures. A healthy devnet start logs lines like these (timestamps omitted):
The dashboard serves /healthz, /api/status/<network>, /api/sol-health/<network> and /api/pool-state/<network>.

Common issues

The keeper refuses to start

Remove CLMM_GAMMA_DECAY_ENABLED if it is set, and check that GAMMA_DECAY_COEF is a number in (0, 1]. On mainnet, also set the collateral mint and the oracle, mint-redeem and CLP program IDs; there is no fallback to devnet values.

It runs only QQQ, skips markets, or fails with UnauthorizedKeeper

  1. Set DEVNET_MINT_REDEEM_PROGRAM_ID or MINT_REDEEM_PROGRAM_ID. Without it, discovery fails and the keeper falls back to QQQ.
  2. Set the collateral mint to cUSDC on devnet (see the warning above).
  3. Check that market.keeper_authority is the keeper wallet’s public key. The admin may have rotated it with update_keeper_authority.
  4. On insufficient-lamports errors, check both wallets. Rent for new pool accounts comes from the CLP authority.

”Financial transactions paused” or UNCONFIRMED_TX_MAY_STILL_LAND

An earlier send ended with an unknown outcome, so the keeper stopped every transaction that moves funds. It re-checks the journaled transaction every 30 s. Once the log shows “Reconciled financial transaction … restart keeper to rebuild plans and resume”, restart it. If the journal is reported unavailable or already owned, configure R2 or a persistent DATA_DIR, and make sure no other process holds the same journal.

RPC rate limits

List several endpoints. After 3 consecutive retriable failures (HTTP 429, 503 or a transport error) an endpoint is skipped for 30 s, then probed again.

A book sits far from NAV

If the underlying market is closed, this is free float by design, and the first fresh price re-centres the book. If the market is open, check CLMM_POOLS_ENABLED and the keeper::clmm_pool logs, then the CLP vault balance if the book’s depth is exhausted. A stale price while the market is open means the source is down or Hyperliquid is vetoing it; check the keeper::oracle logs.

Leftover L or S in the keeper wallet

The keeper no longer sweeps it at boot. Redeem matched amounts with redeem_paired, or a single leg with keeper_redeem_single, signed by the keeper wallet.

See also

Keeper overview

What the keeper runs and which keys it holds.

Peg maintenance

Why the venues stay near NAV and what bounds drift.

Book management

Reposition loop, free float, refill, trim and bid cap.