- The price updater pushes each market’s price and confidence band on-chain, and withholds the push when the source goes stale.
- The book manager (
clmm_pool.rs) centres and reshapes the books, sizes the spread, refills and trims inventory, and posts the buffer, fees, committed spread and volatility decay. - The commit settler cranks committed-order settlement, which anyone may do.
- The liquidity reallocator keeps each market’s CLP vault at the cash it needs before its next top-up, holds the rest in the treasury, and backstops a market’s collateral from the treasury when it falls below 100% or stays short of its target.
- The yield allocator decides how much of each market’s collateral to lend, and where. It lends only on mainnet, and only when switched on.
- The short rebate crank refreshes each market’s rebate rate and, in
livemode, funds the rebate pot.
TwapNotAvailable when the last push is more than 300 s old.
Task inventory
Price updater (every 5 s in the Docker image)
For each market, the keeper fetches a price and confidence band and pushes both withmint_redeem::update_risk_state_v2, together with the source’s publish time. Pyth’s Hermes service needs an API key (PYTH_API_KEY). Without one, the devnet keeper prices markets from a keyless public quote source and derives the band from a GJR-GARCH volatility model. Before it pushes a fresh price, it checks the price against Hyperliquid’s oracle price for the same market and withholds the push if the two disagree. On devnet every market runs in ProxyMode. See Oracle.
The updater knows when a market is closed. If the source’s publish time is more than 120 s old (HERMES_STALE_SECS), or the fetch fails, the keeper withholds the push. The stored price then ages past 300 s, mint and redeem freeze, and the book manager switches to free-float books.
CNBC price holds
The keeper compares every public-quote print with the last price it accepted for that market. A move of more than 10% is held. The keeper doesn’t push it, or anything between the two levels. Mint and redeem freeze on the 300 s gate, and the books float with the held level treated as a pull-only venue. A held print ends in one of three ways:- Reverted. A later print comes back within 1% of the accepted price. The held print was garbage, and the keeper resumes at once.
- Confirmed automatically. This applies only to a reopening gap: the accepted price had been unchanged for an hour, a later print with a later source time and a different price agrees within 0.5%, the move is under 20%, and it isn’t shaped like a split.
- Released by an operator. Everything else needs one, including any move over 10% while the market was trading. The operator checks the price elsewhere, then writes a release object to R2 at
keeper/cnbc-hold-release/v1/<network>/<SYM>.json. The keeper polls it, accepts the first print inside the tolerance and resumes.
scripts/apply-corporate-action.ts, and the keeper then compares prints in adjusted units.
After a restart the baseline for each market is its on-chain price, so a first print more than 10% from the chain is held. Holds page Discord with a reminder every 30 minutes, and are saved to R2 so a restart restores them.
The price updater also withholds any push from a market that is past its scheduled close plus 15 minutes.
Book manager (30 s)
Per market, each tick:- Feeds the vol state with the latest price, ignoring repeated closed-hours prices.
- Centres each book on NAV plus an inventory shift (the GLFT / Avellaneda-Stoikov centre shift). The shift is one dollar amount, added to one leg’s centre and subtracted from the other’s, so the pair price doesn’t move: a lone long buyer pays a small premium that funds a lone short buyer’s discount.
- Repositions a book with
set_pool_shapewhen NAV drifts from its centre by more than a gate. The gate is 20 bps at most, cut down to each leg’s funded quote edge. - Refills and trims both ways. Each side’s bid cUSDC holds at the depth its next free-float book will post ($500 today): the pool vault’s excess goes to the CLP vault, and bids that sellers spent are refilled from it (
clp_fund_pool_usdc). Asks trim to at most 1.5× that target. Nothing refills while a book floats off-hours. - Posts one free-float book per side off-hours, anchored at the last price. Each side puts all its depth in one bin, the outermost within 1% of that price: the r55 study found that bins further out took no ordinary flow and only carried gap risk. The depth is sized to demand, max($500, min(the 99.9th percentile of one side’s measured flow per closed period, a risk cap)). The risk cap limits what a gap at the 99.9th percentile of that market’s daily move could cost the treasury’s share for the market (r61), so a volatile market gets less. There are no fixed dollar ceilings. Until the keeper measures its own fills, flow comes from
CLMM_FREEFLOAT_FLOW_USD_<SYM>and defaults to zero, so every side posts $500. A guard can then pull the bin on the side that other venues show is stale. It never moves the centre or adds depth. - Posts each market’s buffer target (
set_buffer_target): the daily Monte Carlo target from the market’s live state, or a fixed fallback if that is over 48 hours old. The same nightly job publishes each market’s CLP stock share for imbalance control (see Solvency), only when it differs from the value on chain, and each market’s exposure cap (see Exposure caps). Posts the mint/redeem fee (update_market,clamp(fee_mult × half-spread, 10, 100)bps, the same value for both) and the committed spread (set_commit_spread,clamp(half-spread, 10, 500)bps) once they move by at least 5 and 2 bps respectively. The keeper posts a committed spread on all 13 markets, and since 11 Oct 2026 every one accepts committed orders. - Posts the short-leg volatility decay (
set_s_gamma_index). It is always on and has no switch. Charges accrue until they reach a 2e-4 posting gate, and the unposted balance is saved to R2 orDATA_DIR, so a restart doesn’t discard it. A market that hasn’t had a γ write for a day is posted anyway once at least 1e-6 is pending, so slow markets such as FX are charged at least daily. See NAV → Volatility decay. - Runs the cross-market collateral rebalance. It plans at the price the program checks, which is the stored price with γ applied to the short leg, and leaves each source one confidence band of margin above its target. A market within one confidence band of its target is left alone, since the next price can close that gap. Past the band it is filled to its target plus the band. Markets below 100% collateralization are filled first, then markets below their target, and each source’s excess is split across the markets it can reach in proportion to their need. The program clamps each move to the source’s excess above its own target.
Commit settler (20 s idle, 5 s while orders pend)
The settler finds pendingOrder accounts with one filtered getProgramAccounts call, and also wakes early on websocket notifications. It settles up to 10 orders per tick, largest first, and simulates each settle before sending it:
- A refund (a
min_outmiss), or a fill the CLP vault already covers, is sent unchanged. - Every
settle_swapcarries the four exposure accounts: the pool long and short synth vaults, then the CLP’s long and short token accounts. A capped market needs them, and an uncapped one ignores them. The settler reads each simulated settle’s logs and classes it as a fill, a partial fill (settle_swap PARTIAL: the exposure cap took part of the order and refunded the rest), a refund at the exposure cap (settle_swap REFUND exposure cap) or an ordinary refund. - A fill the CLP can’t cover is funded first. The settler borrows the cash it lacks from the treasury (
allocate_to_market) and mints or redeems pairs if needed: a sell pays out cUSDC, and a buy needs synth, which the CLP mints in pairs at twice the value it lacks. These steps go in the same transaction as the settle where they fit, so a fill that fails takes its funding back with it. Otherwise they run in sequence before the settle. - An order that the collateral floor or the oracle checks hold back waits for a later push.
cancel_swap, which returns the escrow to the owner. Only the keeper settles, but anyone may refund. A payout or refund that can’t reach the owner’s recorded account leaves the order held, and the settler then cranks claim, creating the owner’s token account first and getting the claim account’s rent back.
Liquidity reallocator (30 s)
The CLP’s cUSDC sits in the treasury (the global vault) and in each market’s CLP vault. The reallocator is the only task that moves it between them. A CLP vault holds only working cash: what its one instant consumer, the books’ bid refills, can spend before the next tick. That is both sides refilled from empty at the bid target for each book-manager tick in one reallocator tick, plus a quarter of one tick as slack: 2 × $500 × 1.25 = $1,250 per market today. Imbalance control’s pair mint draws its cash from the treasury in the same transaction, and skips when that would leave the treasury below its liquid share. The inventory restocker that kept about $10,000 a side for the retired instant swap was deleted on 13 Oct 2026. Each vault is kept between that need and twice it. Below the need, it is topped up to 1.5×. Above twice the need, everything over 1.5× returns to the treasury. Every move is at least half a tick’s spend, and even at the full spend rate a market needs at most one top-up per tick. An inactive market keeps nothing. The first pass, on 11 Oct 2026, returned $12.99M from 11 markets, $1.18M each, and left them $60,000 apiece. QQQ, SPY and XAU also held about $1.04M each as cash for the instant swap. Onceclp_swap was retired later that day, that cash was no longer needed, and they returned $3.6M to the treasury between them. (pool_swap’s own rolling notional cap on those three markets is a separate setting and is still in place.) The treasury held $22.13M after those returns.
Committed orders need no standing cash, because the settler draws from the treasury when they settle. A shared lock stops the reallocator from returning that cash before the orders settle. Cash waiting in the treasury is available to any market, and the yield allocator counts it as first-loss capital.
Treasury backstop
The reallocator also tops up market collateral from the treasury, withbackstop_market, which pays into the market’s collateral rather than its CLP vault. It reads each market’s collateral ratio at the stored price:
- Below 100%, it fills at once, at least up to the market’s target. Committed orders refuse to fill below the 100% floor, so this is what lets them resume.
- Short of its target by more than one confidence band for 5 minutes, it fills to the target plus the band. That is the cross-market rebalancer’s destination rule, and the rebalancer moves other markets’ excess first.
Treasury withdrawal rate (900 s)
admin_withdraw can take only what the treasury policy’s allowance holds, and the allowance refills at a set rate. This task keeps that rate in proportion to the balance, so a full exit takes 24 hours whatever the treasury holds: an hour’s worth at once, the rest at the rate. On 12 Oct 2026 the treasury held $23.11M, and the rate was still the 64.06 cUSDC a second set when it held $5.53M. The task proposed 256.78 cUSDC a second with a 924,403 cUSDC burst, which applied on 13 Oct 2026. The treasury had grown to $40.0M by late on 12 Oct, so a full exit at that rate took longer than 24 hours until the task proposed a new one. The policy read on chain at 13 Oct 2026 has that rate and burst, with a further change to a limit of 1,000,000,000 cUSDC a second pending until 04:29 UTC on 14 Oct.
The program decides how a change lands. A cut applies at once. A raise waits out the policy’s 24 h delay, during which the guardian can cancel it, and the task applies it once due, so a deposit can’t speed up withdrawals any sooner. The task changes nothing while the allowance is refilling, after a withdrawal in the last hour or a raise just applied, so the rate in force when an exit starts holds until it ends. It moves the limits only when they are more than 10% off, and it leaves alone any pending change that alters the destination, guardian or delay.
Imbalance control (900 s)
After the keeper’s γ charge, a price move changes a market’s claims by exactly (L − S) times the return, where L and S are the values of all its long and all its short tokens. Nothing moves the anchor, so the imbalance β = (L − S)/(L + S) drifts with the price, and the posted buffer has to cover a year of that drift. This task keeps β near zero with the protocol’s own tokens. When β leaves its band (0.05), it redeems the CLP’s surplus of the heavier leg at NAV throughvault_redeem_single. Collateral and claims fall by the same amount and no fee is charged, so no holder’s tokens change value. The cash lands in the CLP vault, and the reallocator returns it to the treasury.
It acts only on a price under 240 s old, so not while a market is closed, and redeems at most $50,000 per market per tick, nothing under $100. It also owns the CLP’s own stock. Each leg keeps a share of the market’s claims, which the daily Monte Carlo job re-derives every night (0.5% until it has). It never redeems a leg below that share, and when both legs sit above it, it trims the matched excess with fee-free redemptions of equal value. The share is the cheapest trade-off between capital held and mint fees saved. It is computed from that night’s re-centring events at the current volatility, 30 days of measured committed-order volume and the live mint fees. Yesterday’s value stays while it is within 10% of the night’s best. When the CLP holds too little of the heavier leg, it first mints pairs, with the cash coming from the treasury in the same transaction (the mint is skipped if that would leave the treasury below its liquid share). The CLP vault’s own cash is the books’ bid budget and is never touched. A pair mint splits the collateral 50/50 by value, so each leg gains the shortfall. Then it redeems the heavier leg’s surplus: in effect a mint of the lighter leg. That costs about twice the mint fee on the value added, and 90% of the fee stays in the market’s collateral.
It has run live since 12 Oct 2026, after a few hours in dry-run. The daily collateral targets plan on it: each market’s buffer is computed with the book re-centred whenever its imbalance leaves the band, for as long as the live imbalance stays within twice the band (see Solvency). A market whose imbalance gets past that is planned as if nothing re-centred it.
Yield allocator (300 s)
The allocator is the only part of the keeper that lends collateral. Each tick it reads the USDC reserves of three mainnet venues straight from chain: Kamino’s main market, Save’s main pool and Jupiter Lend Earn. MarginFi is disabled: the keeper’s decoder expects a 1,864 byteBank, the live account is 3,912 bytes and fails to decode, so it was in no plan anyway, and it was removed on 13 Oct 2026. From each reserve it rebuilds the venue’s interest-rate curve, so it knows how far the supply rate falls as the protocol’s own deposit pushes utilization down. It plans on a blended rate, 25% today’s rate and 75% the venue’s 30-day mean, so a one-day spike doesn’t pull collateral.
Its limits are derived from stated assumptions:
First-loss capital is the capital that can absorb a venue failure. It is the treasury, which the allocator reads on chain every tick, plus 50% of all markets’ collateral above 100% (
YIELD_VENUE_LOSS_BUDGET). The treasury counts because backstop_market can move it into any market’s collateral. With the shares at their defaults, any two venues can lose everything at once and backstop_market restores the markets. Save’s record is a $1.26M bad debt in three isolated pools in November 2022, which a DAO vote repaid; MarginFi’s is a critical flash-loan bug, reported through its bug bounty and patched before anyone used it. On 12 Oct 2026 the shares bound nothing. Kamino, Save and MarginFi were held by their own exit liquidity, and only Jupiter Lend’s cap came from its share. MarginFi has since been removed from the plan.
Per market, only the liquidity budget binds: the liquid share, 3% as the fallback and re-derived each night in the range 1% to 10%. The on-chain rails (yield_enabled and yield_ceiling_bps) apply on top. A market’s ledger has three venue entries, one for each of the three venues, so a market can hold all of them. A market at or below 100% lends nothing.
Inside those limits it fills the venues in order of marginal rate, so every funded venue ends at about the same marginal rate. When the venues can’t take every market’s full budget, each market lends the same share of its budget, so every market keeps the same liquid share. A market in Stress or Recovery recalls everything.
Until 11 Oct 2026 the venue cap was half of each market’s own collateral above 100%. At the 102% target that left about 3% of collateral lendable. On 11 Oct 2026 the new rule was measured against live mainnet venues for the 14 devnet markets of that date, which held $19.2M of collateral. With the $5.53M funded treasury, the plan lent about two-thirds of that collateral, held back by Jupiter Lend’s venue cap of $5.7M. Later that day the liquidity reallocator returned $12.99M of idle CLP cash, taking the treasury to $18.52M and first-loss capital to $18.73M. The plan now lends 69.9%, the same share in every market, and earns 3.07% on all of it, about $589,000 a year. Each market’s liquid share is what binds now, not venue capacity. Kamino and MarginFi stopped at their exit caps, and Jupiter Lend and Save share the rest where their marginal rates meet. On 12 Oct 2026 the protocol’s own holdings were cut to working size, leaving about $1.4M of collateral. At that size no venue cap binds except MarginFi’s small exit cap, and each market’s liquid share decides what is lent (MarginFi was removed from the plan on 13 Oct 2026). That size was chosen partly because the venues couldn’t absorb much more: at a $1M to $5M treasury, the loss shares would let only 10% to 38% of the old $18.3M be lent.
It sends transactions only on mainnet USDC with COLLATERAL_YIELD_ENABLED=true, which is off by default. On every other cluster it plans and reports to the dashboard but sends nothing. The on-chain rails sit on top: yield_enabled and yield_ceiling_bps on each market. Since 12 Oct 2026 every market has yield enabled with its ceiling at 10,000 bps: all idle collateral may be lent, and the keeper recalls it before paying out. The program accepted at most 9,000 bps until then. Study r57 had recommended 9,000, because a 10% cash share pays a 10% run even on a day the lending venues freeze (as Solend’s did in June 2022, during FTX and in July 2023); the operator chose 100%.
Devnet has no real lending, so a yield emulator (DEVNET_YIELD_EMULATION, default on, devnet only) stands in for it. Every hour it mints the cUSDC that the allocator’s mainnet plan would have earned on each market’s collateral, crediting each venue at its 30-day mean rate rather than its spot rate, and pushes it through the real harvest_collateral_yield, which adds it to the market’s total_collateral. When no fresh mainnet snapshot is available, it credits nothing for that hour. The emulator sizes its plan against the same treasury, so emulated yield rose with the new venue cap.
Redemption settler (60 s idle, 10 s while requests pend)
Pays therequest_redemption requests that holders queue when a redeem is larger than the vault’s cash. Per market, oldest request first, it pays the largest part that fits with settle_redemption. If the vault’s cash isn’t enough, it recalls the market’s lending deposits in the same transaction. For what the venues can’t release in time, it takes a treasury advance (advance_to_market) within the market’s on-chain max_advance_bps. When a market has an advance outstanding and no open request, it repays the advance from the vault, recalling first if needed, so the market’s own recall goes back to the treasury before it is lent again. The transactions that need a venue withdraw use an address lookup table when they don’t fit one legacy packet. The task is off unless REDEMPTION_SETTLE_ENABLED is true. See Redeem.
Devnet treasury trim (5 min)
Devnet only, and off unlessDEVNET_TREASURY_TARGET_USD is set. It withdraws what the treasury policy’s allowance allows every tick, never past the target balance, and burns the cUSDC in the same transaction. The treasury’s rate task holds the rate in force while the trim runs.
Exposure caps
Every 30 minutes it posts each market’s exposure cap toClp.oi_cap with update_oi_cap, signed as the CLP authority. The nightly cr-targets job sizes the caps and publishes them as exposure_caps_atoms (cUSDC atoms by symbol) in cr-targets/latest.json. Each tick the primary keeper compares each market’s published cap with the one on chain:
- A decrease posts at once, and so does capping a market that is uncapped. An increase posts only when it exceeds the on-chain cap by more than 1%, so the cap doesn’t churn.
- It fails closed. A missing file, a file over 48 hours old, a market with no entry or a target under $1 leaves the on-chain cap as it is. It never posts 0, because 0 would uncap the market.
EXPOSURE_CAPS_MODEisoff,dryrunorlive.dryrunis the default, and any other value also meansdryrun, so a typo can’t go live. Indryrunit logs what it would post.
Short rebate
About every 16 minutes (960 s,REBATE_INTERVAL_SECS) it runs refresh_rebate for each market’s short rebate, with the exposure accounts the instruction reads. The program moves the ramp’s average only once 900 s have passed since its last step, so the extra minute makes every keeper refresh step it. The instruction is permissionless, so the keeper is only the cranker that is always running. It sets no rate. Only the primary instance runs it.
REBATE_MODE is off, crank or live, and crank is the default.
cranksendsrefresh_rebateonly. The rate and the ramp keep updating, but the pot is not topped up, so accrual slows to whatever the pot already holds.livealso funds the pot withfund_rebate, signed as the CLP authority. It tops the pot up to about a day of need when it falls under half a day of need, and never past the collateral yield the market has harvested since the rebate opened, which the program enforces withRebateFundingAboveYield. The cUSDC comes from the treasury throughadmin_withdraw, in one transaction with thefund_rebate, so it is subject to the treasury’s withdrawal limits. It funds only when the treasury’s withdrawal destination is the CLP authority’s own cUSDC account and the withdrawal allowance covers the amount, and it skips a top-up under $1.
Housekeeping
SOL balance checks every 300 s (with alerts, and airdrop top-ups on devnet), an inventory monitor, and an operator dashboard.The spread engine (model-free)
The half-spread that shapes every book is not a forecasting model. No trained model is in the live path: a linear-quantile forecaster measured net-negative in research, because inside amax() with the floors it could only widen, taxing flow without adding protection. The live spread is a max-of-floors stack:
freeze_trigger, the book freezes: it stops repositioning and floats rather than chasing a disorderly tape. And a protective drift skew widens the trend-adverse side by ×(1 + z), where z is a slow, roughly 1-day return z-score, while the other side never tightens. Backtested, the skew fixes trend bleed (XAU went from −233 to +257 bps/yr). The antisymmetric variant, which also tightened the other side, was falsified.
The vol state lives in memory. After a restart, the min_bps and oracle-confidence floors govern until the ratchet warms, within about 8 windows; the slow floor takes longer. Closed-hours repeated prices are ignored.
Listing without model training
Per-market config (min_bps / floor_mult / fee_mult / window) lives in the keeper’s config JSON; fee_mult is 1.0 on every live market. Listing a new asset needs no training: the floors plus the ratchet carry both safety and competitiveness. The recipe is steep-wing geometry sized to the asset’s vol class, min_bps at least about 2× its 5-minute σ, and fee_mult around 1.0 to 1.5 for high-vol single names.
Configuration
Most keeper behavior sits behind env switches that default on. γ decay has none.
The keyless fallback price source has its own switch, which is off by default. The devnet keeper runs with it on.
Main tunables:
Full list: keeper running guide.
Can I run my own keeper?
Not the protocol’s: its source isn’t published, and its privileged instructions check the market’s authorities. What’s open and what isn’t:settle_swapis keeper-only: the signer must beclp.authorityormarket.keeper_authority.cancel_swapandclaimare open to anyone.- Paired arbitrage is open. Ordinary
mint_paired/redeem_pairedplus book trades need no privilege, and neither doesdonate_to_vault. - Pricing and book shaping are privileged.
update_risk_state_v2,set_buffer_targetandset_s_gamma_indexrequiremarket.keeper_authority.set_pool_shaperequires the pool’sauthority, fee posting throughupdate_marketrequiresmarket.authority, andset_commit_spreadrequires the CLP’sauthority.
Dashboard
The keeper serves an operator dashboard on one HTTP port (PORT, then DASHBOARD_PORT, default 8484). It shows price freshness per market, risk state, book positions and drift, pending committed orders, vault balances and yield status. It is for the operator; integrators read state from on-chain accounts.
Read more
Keeper overview
Architecture, loops, and operational detail.
Keeper configuration
Config, keypairs, dashboard, troubleshooting.
CLP and the venues
The books and vaults these loops manage.
Solvency
What the protocol enforces independently of the keeper.

