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

# clp

> Continuous Liquidity Provider - the protocol's vaults, native bin-book pools, and the v2 swap venues.

The CLP program is Continuum's treasury and market maker. It holds protocol LP capital, runs the v2 trading venues - native concentrated-liquidity pools for instant swaps and a commit-then-settle path for size - and mints/redeems its own L+S inventory via CPI into mint-redeem. Per-market `Clp` PDAs hold inventory budgets, the OI cap, and swap-guard state. A single `GlobalClp` PDA tracks total deposits and routes capital to per-market vaults.

```
Devnet ID: 8xauDRjw9XRyk4FE3hW1JKjD8nC87gfr59Xig1dJqLES
```

User-facing instructions: `pool_swap`, `clp_swap`, `commit_swap`, `settle_swap` (permissionless crank), and `cancel_swap`. Everything else is gated by admin or keeper authority.

Liquidity lives inline in each market's native `Pool` accounts (no per-bin rent), and repositioning a book is a single transaction.

## The two swap venues

| Venue | Instruction | Price | Best for |
| - | - | - | - |
| **Pool book** (instant) | `pool_swap` | Walks the bin ladder around NAV - real slippage | Small/urgent trades |
| **Committed orders** | `commit_swap` → `settle_swap` | NAV ± 10 bps at the *next* oracle push | Any size |

The pool book is deliberately lean - posted depth is a written option against latency arbitrage, and idle bid cUSDC forgoes carry, so the keeper sizes it to organic demand, not to TVL. Committed orders carry the size: because the fill price is set strictly *after* the commitment, latency arbitrage is structurally impossible and no depth needs to be posted in advance.

`clp_swap` (the oracle-pegged swap at NAV ± spread from CLP inventory) remains live as a fallback to the pool book.

## Accounts

### `GlobalClp` PDA

```
seeds = [b"global_clp"]
```

One per cluster.

| Field | Type | Purpose |
| - | - | - |
| `total_deposits` | u64 | Cumulative admin-funded principal (net of `admin_withdraw`) |
| `last_admin_fund_at` | u64 | Last `admin_fund` unix-ts (drives the withdraw cooldown) |
| `total_fees_accumulated` | u64 | Lifetime protocol fees |
| `market_allocations` | array of `MarketAllocation` | Per-market cUSDC outstanding |
| `protocol_buffer` | `ProtocolBuffer` | Yield, losses, current balance, target size, above-target flag |
| `liquidity_seed_threshold` | u64 | Below this, the seeder skips |
| `authority` | Pubkey | Admin signer |
| `admin_wallet` | Pubkey | Receives the TVL-tiered dev tax |

### `Clp` PDA

```
seeds = [b"clp", market_id]
```

One per market.

| Field | Type | Purpose |
| - | - | - |
| `market_id` | Pubkey | Bound market PDA |
| `oi_cap` / `current_oi` | u64 | Hard ceiling on `total_l_supply` and its mirror |
| `inventory_budget` | `InventoryBudget` | Soft/hard q-bps + drawdown bounds |
| `inventory_state` | `InventoryState` | Current q-bps, breach flags, peak value |
| `yield_strategy` / `yield_deployments` / `total_deployed` | various | Yield scaffolding (off by default) |
| `oracle_config` | Pubkey | Bound oracle PDA |
| `vault_l_deployed` / `vault_s_deployed` | u64 | L/S outstanding from CLP-side minting |
| `total_vault_pairs_minted` | u64 | Lifetime pair-mint volume |
| `allocated_from_global` | u64 | cUSDC allocated from the global vault |
| `swap_guards` | `SwapGuardsState` | v2 swap circuit-breaker config (below) |
| `swap_window_start` / `swap_window_used` | i64 / u64 | Rolling notional-cap window state |
| `swap_prev_price` | u64 | Last NAV accepted by a swap (deviation anchor) |
| `authority` | Pubkey | Keeper/admin signer |

### `SwapGuardsState`

Persisted on `Clp`; configured via `configure_swap`. The all-zero default keeps swaps off until configured.

| Field | Type | Purpose |
| - | - | - |
| `enabled` | bool | Master on/off for the native swap path |
| `frozen` | bool | Hard kill switch |
| `spread_bps` | u16 | LP edge applied to NAV (30 bps today on the instant path) |
| `max_staleness_secs` | i64 | Reject if NAV/TWAP older than this |
| `max_confidence_bps` | u16 | Reject if oracle confidence exceeds this (bps of price) |
| `max_deviation_bps` | u16 | Reject if NAV moved more than this vs the last accepted price |
| `window_cap_usd` | u64 | Rolling-window notional cap (cUSDC, 6-dec); 0 = uncapped |
| `window_secs` | i64 | Rolling-window length |
| `collateral_floor_bps` | u16 | Minimum market collateralization that must hold after a swap |

### `Pool` PDA

```
seeds = [b"pool", market_id, [side]]    // side: 0 = long, 1 = short
```

One per (market, side) - a native concentrated-liquidity book for one synth side vs cUSDC. The entire book lives inline in the account (max 16 bins per side, `POOL_MAX_BINS`), so there is no per-bin rent. Two pool-owned token accounts hold the actual reserves: a synth vault (asks) and a cUSDC vault (bids).

| Field | Type | Purpose |
| - | - | - |
| `authority` | Pubkey | Keeper signer for `set_pool_shape` / `configure_pool_geom` |
| `market` / `side` | Pubkey / u8 | Binding |
| `synth_mint` / `usdc_mint` | Pubkey | Pair mints |
| `base_price_1e6` | u64 | Active price ≈ side NAV, set by the keeper |
| `step_bps` | u32 | Core bin step (`r = 1 + step_bps/1e4`) |
| `num_bins` | u8 | Bins per side (≤ 16) |
| `asks` | `[u64; 16]` | Synth reserve per ask bin (above NAV) |
| `bids` | `[u64; 16]` | cUSDC reserve per bid bin (below NAV) |
| `enabled` / `frozen` | bool | Trading gates |
| `shape_updated_at` | i64 | Last reposition unix-ts |
| `wing_step_bps` | u32 | Wing step for the 2-zone geometry (0 = uniform ladder) |
| `core_bins` | u8 | Number of fine core bins before the wings start |

**Bin geometry.** Ask bin `i` sits at `base · r^(i+1)`, bid bin `i` at `base · r^-(i+1)`. The active price itself is the empty mid, so the minimum round trip costs \~2 steps. With 2-zone geometry the first `core_bins` steps use `step_bps` (fine - precise pricing near NAV) and the rest use `wing_step_bps` (wide - the same 16 bins span the full float range).

### `Order` PDA (committed orders)

```
order:  seeds = [b"order", user, market, nonce_le_u64]
escrow: seeds = [b"escrow", order]      // token account owned by the order PDA
```

A commit-then-settle order: the user escrowed `amount_in` at `created_at`, and the fill executes at the first oracle push strictly after that.

| Field | Offset | Type | Purpose |
| - | - | - | - |
| `user` | 8 | Pubkey | Order owner (memcmp filter offset) |
| `market` | 40 | Pubkey | Bound market (memcmp filter offset) |
| `side` | 72 | u8 | 0 = long, 1 = short |
| `direction` | 73 | u8 | 0 = buy synth, 1 = sell synth |
| `status` | 74 | u8 | 0 = pending - the only persisted state |
| `bump` / `escrow_bump` | | u8 | PDA bumps |
| `amount_in` | | u64 | Escrowed atoms (cUSDC for buys, synth for sells) |
| `min_out` | | u64 | Slippage floor - settle *refunds* (not reverts) on a miss |
| `created_at` | | i64 | Commit time - settle requires `twap_updated_at > created_at` |
| `expires_at` | | i64 | `created_at + 180`; settle owns ≤, cancel owns > |
| `nonce` | | u64 | Client-supplied PDA seed component |

The byte offsets are load-bearing: the keeper discovers pending orders with `getProgramAccounts` memcmp filters at `user@8`, `market@40`, `status@74`. Every terminal path (settle, refund, cancel) closes both the order and the escrow, returning all rent to the user - which also makes concurrent settle attempts idempotent (the loser fails at account load).

### `AdminWithdrawWindow` PDA

```
seeds = [b"admin_withdraw_window"]
```

Rolling 24h cap state for `admin_withdraw`: `window_start_at`, `cumulative_in_window`.

## Instructions

### Swaps (permissionless)

#### `pool_swap(direction: u8, amount_in: u64, min_out: u64)`

Swap against a pool's bin book. `direction`: 0 = buy synth (pay cUSDC), 1 = sell synth (receive cUSDC). The handler walks the bins - buys consume ask bins upward from `base·r`, sells consume bid bins downward from `base/r` - decrementing reserves in place so the stored book always matches the vault balances. Partial fills round in the pool's favor. Rejects with `SwapDisabled` if the pool is disabled or frozen, `SlippageExceeded` if output \< `min_out`, and `InsufficientFunds` on an empty book. The swap's cUSDC notional is also charged against the CLP's rolling-window cap (`swap_guards.window_cap_usd`; `0` = uncapped), so it can't drain the venue faster than the configured budget.

Accounts: `user` (signer), `user_usdc`, `user_synth`, `pool`, `clp` (per-market CLP PDA, seeds `[b"clp", pool.market]` — carries the swap-guard + window-cap state), `synth_vault`, `usdc_vault`, token program. The pool PDA signs the outbound transfer. **The `clp` account is required** (added for the notional cap); a client that omits it fails account resolution.

#### `clp_swap(side: u8, direction: u8, amount_in: u64, min_out: u64)`

The oracle-pegged instant swap: trade cUSDC ↔ one synth side at NAV ± `spread_bps`, filled from CLP inventory. Delta-neutral by construction - the tokens being moved are already-outstanding, fully-backed supply, so total supply and collateral are unchanged; only ownership moves. This path never mints; the keeper replenishes inventory separately via `vault_mint_pairs`.

Every call runs the full guard battery from `SwapGuardsState`: enabled/frozen, NAV staleness, oracle confidence, deviation vs the last accepted price, the rolling-window notional cap, and the market-level collateral floor.

#### `commit_swap(side: u8, direction: u8, amount_in: u64, min_out: u64, nonce: u64)`

Open a committed order: escrow `amount_in` (cUSDC for buys, synth for sells) into the order-owned escrow account. The payout ATA must exist at commit time so settlement can never strand on a missing account. Fails fast if the market is inactive or swaps are disabled/frozen (settle re-checks authoritatively). Emits `OrderCommitted`.

Constants: `COMMIT_TTL_SECS = 180`, `COMMIT_SPREAD_BPS = 10`. The committed spread is thinner than the instant path's because a fill priced strictly after the commitment carries no adverse selection - there is no stale quote to select against.

#### `settle_swap()` (permissionless crank)

Fill a pending order at NAV(now) ± 10 bps. Anyone may crank it; the keeper does in practice. Valid only when:

* `now ≤ expires_at` (else `OrderExpired`),
* `market.twap_updated_at > order.created_at` - the freshness rule that makes latency arbitrage impossible (else `NoFreshOraclePush`),
* the oracle health guards pass, and the market collateral floor holds.

Outcome semantics:

| Condition | Result |
| - | - |
| Quote ≥ `min_out` | Fill - escrow to CLP, payout to user, accounts close, `OrderSettled` |
| Quote \< `min_out` | **Full refund** (terminal, accounts close, `OrderRefunded`) - a price that ran away must not leave the order revert-looping until expiry |
| CLP inventory short | Revert - order stays pending; the keeper provisions inventory and retries |

Committed fills advance the deviation anchor (`swap_prev_price`) but do **not** consume the rolling window cap - the cap bounds stale-price extraction, which a committed fill carries none of.

<Warning>
  Mainnet prerequisite (flagged in the code): settlement currently reads the keeper-relayed `user_twap_price`. Before any mainnet deploy it must read Pyth directly and enforce `publish_time > order.created_at`.
</Warning>

#### `cancel_swap()` (order owner)

Refund an expired order. Allowed strictly after `expires_at` (settle owns ≤, cancel owns > - no gap, no overlap; else `OrderNotExpired`). Deliberately checks nothing else - no frozen/enabled/oracle/active gates: escrowed funds are always recoverable after the TTL regardless of protocol state. Returns the escrow and all rent to the user, emits `OrderCancelled`.

### Native pool management (admin / keeper)

#### `init_pool(side: u8, step_bps: u32, num_bins: u8)` (admin)

Create the per-(market, side) `Pool` PDA. `step_bps` ∈ (0, 1000], `num_bins` ∈ \[1, 16]. The pool starts disabled with an empty book; the first `set_pool_shape` enables it.

#### `set_pool_shape(base_price_1e6: u64, asks: Vec<u64>, bids: Vec<u64>)` (keeper)

The reposition: set the active price (≈ NAV) and the per-bin reserves in one call. Each vector must be exactly `num_bins` long and sum to no more than the corresponding vault balance. This is also how the book recaptures LVR - repricing to NAV instead of being walked there. The keeper shapes the book with a geometric decay per side, typically skewed (protective skew: thin/steep asks, deeper bids for an up-drifting asset).

#### `configure_pool_geom(wing_step_bps: u32, core_bins: u8)` (keeper)

Configure the 2-zone bin geometry: fine `core_bins` near NAV at the pool's `step_bps`, then `wing_step_bps` for the outer wings. Validated: wing ≥ core step and `core_bins ≤ num_bins`. `wing_step_bps = 0` resets to the uniform ladder.

#### `realloc_pool()` (pool authority)

Grow a pre-2-zone `Pool` account to the current layout. Raw realloc with zero-fill - new fields land as 0, which `bin_geom()` reads as the uniform ladder, so legacy pools keep behaving until configured.

#### `pool_sweep_usdc(amount: u64)` / `pool_sweep_synth(amount: u64)` (keeper)

Sweep accumulated cUSDC out of a pool's bid vault back to the per-market CLP vault, or trim excess synth from the ask vault back to CLP custody. One-directional buy flow converts ask inventory into cUSDC that piles up in the pool; the sweep recycles it through `vault_mint_pairs` into fresh L+S (the elastic refill). The synth sweep exists because ask depth far beyond the bid side's value is pure arb bait - trimmed synth serves committed-order provisioning instead.

#### `clp_fund_pool_synth(amount: u64)` (keeper)

Move synth from the CLP's L/S ATA into a pool's ask vault - the refill leg after `vault_mint_pairs`. The inventory was already counted at mint time, so no counters change; this just stages tokens for the next `set_pool_shape`.

### Mint/redeem inventory (keeper)

The CLP mints and redeems on its own behalf via CPI into mint-redeem, with the CLP PDA acting as the "user".

#### `vault_mint_pairs(usdc_amount: u64)`

CPI into `mint_redeem::mint_paired` using per-market vault cUSDC. Minted L+S land in the CLP PDA's ATAs; `vault_l_deployed` / `vault_s_deployed` advance by the actual deltas.

#### `vault_redeem_pairs(l_amount: u64, s_amount: u64)`

Reverse: burn L+S held by the CLP PDA, cUSDC returns to the per-market vault.

#### `vault_redeem_single(is_long: bool, token_amount: u64)`

Redeem one side at NAV via `mint_redeem::redeem_single_for_vault`. Used by the pool depth balancer to convert one side's excess into cUSDC for re-pairing. The inner instruction enforces a post-redeem collateralization floor of 100%, so an underwater vault refuses the operation.

#### `clp_burn_excess(is_long: bool, amount: u64)` (admin)

Burn asymmetric L or S residue held by the CLP without any collateral payout (CPI into `permissionless_burn`). Recovery/cleanup path.

### Capital flow

#### `admin_fund(amount: u64)` (admin)

Deposit cUSDC from the admin wallet into `global_clp_vault`. Advances `total_deposits` and stamps `last_admin_fund_at`.

#### `admin_withdraw(amount: u64)` (admin)

Withdraw idle cUSDC from the global vault. Two protections against a compromised admin key: a rolling 24h cap of 10% of cumulative funded principal, and a 1h settlement cooldown after any `admin_fund` (so the cap denominator can't be flash-pumped). Capital already allocated to markets must first come back via `return_to_global`.

#### `allocate_to_market(amount: u64)` / `return_to_global(amount: u64)` (keeper)

Move cUSDC between `global_clp_vault` and a per-market `clp_vault`, updating `allocated_from_global` and the global per-market allocation counters.

#### `deposit_profit(amount: u64)` (keeper)

Route arb/spread profit from the keeper wallet into the global vault, minus a TVL-tiered dev tax to `admin_wallet`: 15% below \$5M TVL, 10% to \$25M, 5% above.

#### `collect_fees(amount: u64)` (keeper)

Transfer trading fees into the per-market vault; advances `total_fees_collected`.

### Configuration (admin)

* `configure_swap(enabled, frozen, spread_bps, max_staleness_secs, max_confidence_bps, max_deviation_bps, window_cap_usd, window_secs, collateral_floor_bps)` - all args optional; `None` leaves a field unchanged. `frozen = true` is the hard kill switch.
* `update_oi_cap(new_cap)` - per-market OI ceiling.
* `configure_inventory_budget(...)` - soft and hard q-imbalance + drawdown bounds.
* `configure_yield_strategy(...)` - per-state deployment percentages (yield is off by default).
* `configure_liquidity_seeder(threshold)` - below `threshold` the seeder skips. Default \$1k.
* `realloc_clp()` - grow a pre-upgrade `Clp` account to the current layout.

### Inventory ops

* `update_inventory(volatile_value, stable_value)` (keeper) - recompute `current_q_bps`, drawdown, and breach flags.
* `update_oi(new_oi)` (keeper) - mirror `market.total_l_supply` into `clp.current_oi`; rejects past `oi_cap`.
* `check_inventory_restrictions()` / `get_yield_status()` - read-only helpers, CPI-callable.

### Yield (scaffolded, off)

`record_deployment` / `record_withdrawal` / `record_harvest` track deployments to lending venues; `add_yield_to_buffer` / `absorb_loss` maintain the global protocol buffer, which absorbs losses before they hit user collateral. All off by default.

## Engineering note: Boxed accounts and the 4KB stack

The SVM caps each stack frame at 4096 bytes. Instructions with many deserialized `TokenAccount`/`Mint` payloads (`commit_swap`, `settle_swap`, `vault_mint_pairs`, ...) heap-`Box` their accounts, and the mint-redeem CPIs are hand-rolled `invoke_signed` calls instead of generated `CpiContext`s - a large CPI context on the outer frame pushes it past the limit, which surfaces as a runtime access violation rather than a compile error. If you fork or extend the program, keep new account structs boxed.

## Errors

| Code | Name | Cause |
| - | - | - |
| 6000 | `Unauthorized` | Signer mismatch |
| 6001 | `WithdrawalLocked` | Legacy lockup path |
| 6002 | `InvalidAmount` | Zero or out-of-range argument |
| 6003 | `SwapDisabled` | Swap path or pool not enabled |
| 6004 | `SwapFrozen` | Circuit breaker engaged |
| 6005 | `InvalidSide` | Side/direction not 0 or 1 |
| 6006 | `SwapAccountMismatch` | Token account mint/owner doesn't match the market binding |
| 6007 | `MarketInactive` | `market.is_active = false` |
| 6008 | `SlippageExceeded` | Output below `min_out` (instant paths) |
| 6009 | `OrderExpired` | Settle attempted after the TTL - use `cancel_swap` to refund |
| 6010 | `OrderNotExpired` | Cancel attempted before the TTL - wait for settlement or expiry |
| 6011 | `NoFreshOraclePush` | No oracle push strictly newer than the commit yet |
| 6012 | `OracleStale` | NAV/TWAP older than `max_staleness_secs` |
| 6013 | `OracleConfidenceTooWide` | Confidence above `max_confidence_bps` |
| 6014 | `OracleDeviationTooLarge` | NAV moved more than `max_deviation_bps` vs last accepted |
| 6015 | `WindowCapExceeded` | Rolling notional cap hit (instant path only) |
| 6016 | `CollateralFloorBreach` | Market under `collateral_floor_bps` |
| 6017 | `InsufficientShares` | LP-share path (legacy) |
| 6018 | `InsufficientFunds` | Vault/inventory can't cover the request |
| 6019 | `InsufficientLiquidFunds` | Funds deployed; recall first |
| 6020 | `OICapExceeded` | New OI > cap |
| 6021 | `MathOverflow` | Should be unreachable |
| 6022 | `InsufficientBuffer` | Buffer can't absorb loss |
| 6023 | `HardBoundBreach` | q or drawdown past hard bound |
| 6024 | `YieldStrategyDisabled` | Yield off |
| 6025 | `ExceedsMaxDeployment` | Exceeds per-state deployment cap |
| 6026 | `MaxDeploymentsExceeded` | Deployment array full |
| 6027 | `DeploymentNotFound` | Unknown protocol |
| 6028 | `InsufficientDeployment` | Recall > deployed |
| 6029 | `DeploymentTooHigh` | % > config |
| 6030 | `OracleReadError` | Bound oracle unreadable |
| 6031 | `InvalidPool` | Pool pubkey mismatch |
| 6032 | `PoolNotConfigured` | Pool not yet wired/initialized |
| 6033 | `AdminWithdrawCapExceeded` | 24h cap or fund cooldown hit |
| 6034 | `InvalidPosition` | Position/PDA pubkey mismatch |
| 6035 | `InvalidAccountData` | Account data too small |

→ [Full error catalog](/reference/errors)

## TypeScript

```typescript theme={null}
import { Program, BN } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import clpIdl from "./clp.json";

const clp = new Program(clpIdl, provider);

const [clpPDA] = PublicKey.findProgramAddressSync(
  [Buffer.from("clp"), marketPDA.toBuffer()],
  clp.programId,
);
const [longPoolPDA] = PublicKey.findProgramAddressSync(
  [Buffer.from("pool"), marketPDA.toBuffer(), Buffer.from([0])], // 0 = long side
  clp.programId,
);

const [perMarket, pool] = await Promise.all([
  clp.account.clp.fetch(clpPDA),
  clp.account.pool.fetch(longPoolPDA),
]);

// Note the camelCase quirk: Anchor renders base_price_1e6 as basePrice1E6 (capital E).
console.log("Active price:", pool.basePrice1E6.toString());
console.log("Asks:", pool.asks.map((a: BN) => a.toString()));
console.log("OI cap:", perMarket.oiCap.toString(), "current:", perMarket.currentOi.toString());
console.log("Swap spread (bps):", perMarket.swapGuards.spreadBps);
```

→ [Committed-order and pool\_swap examples](/build/example-mint-redeem#swapping-on-the-clp)

## See also

<CardGroup cols={2}>
  <Card title="CLP concept" icon="book" href="/concepts/clp">
    Why CLP exists, the capital flow, the inventory bounds.
  </Card>

  <Card title="Reading state" icon="magnifying-glass" href="/flows/read-state">
    OI utilization, vault balances, pool books.
  </Card>
</CardGroup>
