> ## 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.

# mint-redeem

> User-facing mint and redeem at NAV. Keeper-only single-sided instructions. Market lifecycle.

The user-facing entry to Continuum. Holds the `Market` PDA, mints L+S SPL tokens, and exposes the privileged keeper-only single-sided ixns used inside arb cycles.

```
Devnet ID: 5MBjhNUUguLTPNR5WG6YBUUw7vUcxQ14ARw3NsS3rKu4
```

## Accounts

### `Market` PDA

```
seeds = [b"market", asset_symbol_bytes]
```

The central account for one synthetic asset.

| Field | Type | Purpose |
| - | - | - |
| `authority` | Pubkey | Admin signer for `update_market` |
| `fee_recipient` | Pubkey | Receives mint/redeem fees (cUSDC ATA) |
| `asset_symbol` | string | Market ticker (e.g., `"QQQ"`) |
| `oracle_address` | Pubkey | Pointer to `OracleConfig` PDA |
| `oracle_type` | enum | `Pyth` / `Hermes` / `Switchboard` / `Manual` |
| `long_mint` | Pubkey | L SPL mint |
| `short_mint` | Pubkey | S SPL mint |
| `collateral_mint` | Pubkey | cUSDC mint |
| `collateral_vault` | Pubkey | Points at the **one unified** cUSDC vault (owned by the `CollateralAuthority` PDA), shared by all markets |
| `multi_collateral_enabled` | bool | If true, multiple collateral types accepted |
| `mint_fee_bps` / `redeem_fee_bps` | u16 | Fees in basis points (10 = 0.10%) |
| `total_l_supply` / `total_s_supply` | u64 | Outstanding supply (6 decimals) |
| `total_collateral` | u64 | This market's collateral **ledger** (6 decimals). With the unified vault this scalar (not the live token balance) is the per-market solvency source of truth; per-market isolation = `total_collateral − deployed_to_yield`. |
| `is_active` | bool | If false, all mint/redeem reject |
| `risk_state` | enum | `Normal` / `ProxyMode` / `Stress` / `Recovery` |
| `risk_state_updated_at` | i64 | Last mirror update unix-ts |
| `user_twap_price` | u64 | Source of `L_NAV` (6 decimals) |
| `twap_updated_at` | i64 | Last TWAP write unix-ts |
| `oracle_confidence` | u64 | Last published confidence (6 decimals) |
| `keeper_authority` | Pubkey | Privileged signer for fee-free single-sided ops |
| `dev_tax_pct` | u8 | Slice of the fee routed to fee\_recipient (default 10%) |
| `buffer_target_bps` | u16 | Keeper-posted dynamic overcollateralization buffer (bps over parity) |
| `s_gamma_index` | u64 | Short-leg volatility-decay scalar, 1e9 fixed (`1e9` = 1.0 = no decay; `0`/unset reads as 1.0). Multiplies `S_NAV`. Ratchet-down only, floored at `0.5`. See [NAV → Volatility decay](/concepts/nav#volatility-decay). |
| `yield_ceiling_bps` / `deployed_to_yield` / `yield_enabled` / `total_yield_harvested` | various | Collateral-yield management state |

### `UserCollateralPosition` PDA

```
seeds = [b"user_collateral", owner, market]
```

Lazy account for users who opt into multi-collateral mode (where the market accepts more than one collateral type, weighted). Most markets don't use this.

| Field | Type | Purpose |
| - | - | - |
| `owner` | Pubkey | Position owner |
| `market` | Pubkey | Bound market |
| `long_amount` / `short_amount` | u64 | Outstanding L+S held by this position |
| `collateral_type` | enum | Which collateral was used |
| `collateral_mint` | Pubkey | Which mint |
| `collateral_amount` | u64 | Total cUSDC-equivalent deposited |

### `FeeWaiver` PDA

```
seeds = [b"fee_waiver", donor, market]
```

A grant created by `donate_to_vault` that lets the donor mint/redeem fee-free for some duration.

| Field | Type | Purpose |
| - | - | - |
| `donor` | Pubkey | Waiver owner |
| `market` | Pubkey | Bound market |
| `waiver_end_at` | i64 | Expiry unix-ts |
| `total_donated` | u64 | Cumulative donation |
| `total_gap_closed_bps` | u64 | Cumulative fee-equivalent saved |

## Fees

Base mint and redeem fees are **10 bps** each. The keeper posts vol-informed updates through the admin-signed `update_market`: `fee = clamp(max(10, fee_mult × q90), 10, 100)` bps, where `q90` is the market's trailing realized move quantile - 10 bps in calm regimes, up to 100 bps in storms. The fee is the at-NAV venue's defense against gap risk; it moves with measured volatility, not a forecast.

The split: `dev_tax_pct` (default 10%) of each fee goes to the `fee_recipient`'s cUSDC account; the remaining \~90% stays in the collateral vault as extra backing. Retained fees are real collateral and count toward `total_collateral`.

## Price freshness gate

In any non-Normal risk state (all devnet markets run in `ProxyMode`), pricing comes from the keeper-relayed `market.user_twap_price`. Every ratio- or price-dependent instruction - `mint_paired`, `redeem_paired`, the keeper single-side paths, donations, excess withdrawals, rebalances - rejects with `TwapNotAvailable` when `twap_updated_at` is older than `MAX_TWAP_AGE_SECS = 300` (5 minutes). A delayed keeper can't be quoted against a stale price.

## Instructions

### User-facing (anyone can call)

#### `mint_paired(collateral_amount: u64)`

Deposits `collateral_amount` cUSDC, mints matched L and S to the user's ATAs. Fee `collateral_amount × mint_fee_bps / 10_000`; the dev-tax slice goes to `dev_token_account`, the rest stays in the vault.

→ [Full mint flow](/flows/mint)

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

Burns L and S in independent quantities, returns `l_amount × L_NAV + s_amount × S_NAV - fee` cUSDC.

**Solvency haircut.** The gross payout is `nominal × min(1, CR)` where CR is the vault's live collateralization at the same NAV. While the vault is at or above 100% (the normal state) this is a no-op. If a large NAV move ever left it under 100%, every redeemer is scaled by the same factor - no first-mover advantage, no bank-run race, and the ratio is preserved for everyone who stays.

→ [Full redeem flow](/flows/redeem)

#### `mint_paired_with_waiver(collateral_amount: u64)` / `redeem_paired_with_waiver(l_amount: u64, s_amount: u64)`

Same as the above but consume an active `FeeWaiver` to skip the fee. Requires the market vault to be over-collateralized (≥102%) for the waiver to apply.

#### `donate_to_vault(amount: u64)`

Donate cUSDC into the market's collateral vault. Creates or extends a `FeeWaiver` for the donor. Used by power users to earn fee-free mint/redeem in exchange for boosting vault collateralization.

#### `permissionless_burn(is_long: bool, amount: u64)`

Burns L or S from the caller's own token account without any collateral payout, decrementing the supply ledger. Only the token-account owner may burn (delegates are rejected). Per-token collateralization strictly improves. Used for voluntary forfeiture and by the CLP's `clp_burn_excess` CPI to retire asymmetric inventory residue.

### Keeper-privileged (signer = `market.keeper_authority`)

#### `keeper_mint_single(is_long: bool, collateral_amount: u64)`

Fee-free, single-sided mint at NAV. Deposits `collateral_amount` cUSDC, mints only the chosen side's tokens at NAV. Used inside arb cycles.

| Argument | Effect |
| - | - |
| `is_long = true` | Receive `collateral_amount / L_NAV` long tokens |
| `is_long = false` | Receive `collateral_amount / S_NAV` short tokens |

No mint fee, no worst-case quoting markup. The keeper pays full NAV for one side, so the vault grows by exactly the value of the new tokens - per-token collateralization is preserved at any size. (An absolute stress-floor check on this path was tried and removed; the error variant `SingleSideMintStressFloor` survives in the IDL but is not enforced.)

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

Fee-free, single-sided redeem at NAV. Burns `token_amount` of the chosen side, returns `token_amount × NAV` cUSDC. Enforces a post-redeem floor of CR ≥ 100% - peg-keeping operates freely while the vault is solvent, and refuses to make an underwater vault worse.

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

Burn only - no cUSDC transfer. Residue cleanup when an arb cycle leaves unsold inventory; the vault keeps the mint-time deposit, the keeper eats the cost.

#### `redeem_single_for_vault(is_long: bool, token_amount: u64)` (CLP CPI only)

Mirror of `keeper_redeem_single` for the CLP's own inventory: burns from CLP-PDA-owned ATAs, pays cUSDC into the per-market `clp_vault`. The signer must be the canonical CLP PDA (`[b"clp", market]` under the CLP program ID), entered via the CLP program's `vault_redeem_single`. Same CR ≥ 100% post-redeem floor.

#### `update_risk_state(new_state, user_twap_price, oracle_confidence)`

The hot path: mirrors oracle health into `risk_state` and writes the spot price + confidence the whole program prices against. Two hard bounds protect against a compromised keeper key: the price may move at most 25% per call (`MAX_TWAP_MOVE_BPS`), and `Stress` can only de-escalate through `Recovery` (where the fee multiplier unwinds over an hour) - never jump straight to `Normal`.

#### `set_buffer_target(buffer_target_bps: u16)`

Posts the market's **dynamic overcollateralization buffer** - the per-market solvency target that replaced the static 102%. The keeper sizes it from the volatility regime and live inventory; on-chain it is clamped to \[100, 5000] bps over parity (target ratio 101%-150%), and 0/unset falls back to the 200 bps (102%) default. This target gates donations, fee waivers, excess withdrawals, and cross-market rebalances.

#### `set_s_gamma_index(s_gamma_index: u64)`

Posts the **short-leg volatility-decay scalar** γ (1e9 fixed). The keeper ratchets it down by realized per-cycle variance, marking `S_NAV` down to bank choppiness-convexity as collateral surplus. On-chain it **rejects any value above the current γ** (ratchet-down only — see `GammaMustRatchetDown`) and clamps to a `0.5` floor. Gated off-chain by `CLMM_GAMMA_DECAY_ENABLED` (default **on** as of June 2026 — it's solvency-critical; set `=false` to disable). Does not touch the long leg. See [NAV → Volatility decay](/concepts/nav#volatility-decay).

#### `rebalance_collateral(amount: u64)`

Cross-market collateral move. With the unified vault this is a **scalar-only ledger move** — no cUSDC token transfer happens; it shifts `total_collateral` from an over-collateralized market to an under-collateralized one. The **source floor** is enforced on-chain: the source market must remain at or above its *own* dynamic target after the move, and only its liquid share (`total_collateral − deployed_to_yield`) may move. (The old TWAP-freshness gate was removed — with no token movement the source-CR floor is the real guard, and gating on a closed-market TWAP only produced spurious `OraclePriceUnavailable` reverts.) Both markets must share the keeper authority.

#### `deploy_collateral_to_yield(amount, protocol)` / `recall_collateral_from_yield(amount, protocol)`

Move idle collateral to an external lending venue and back. Deployment is capped at `yield_ceiling_bps` of `total_collateral` (default 60%); the rest stays liquid for redemptions. The keeper composes the actual lending CPI in the same transaction.

#### `harvest_collateral_yield(yield_amount: u64)`

Routes net interest from a yield recall to the CLP vault as protocol revenue. Principal returns via `recall_collateral_from_yield`; this instruction forwards only the earned spread and advances `total_yield_harvested`.

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

Realizes a loss on collateral deployed to a lending venue. `recall_collateral_from_yield` only decrements `deployed_to_yield` by what physically returns and never touches `total_collateral`, so if a venue freezes or loses principal the vault would keep reporting phantom backing — early redeemers exit at par while stragglers eat the loss. This instruction decrements **both** `deployed_to_yield` and `total_collateral` by `amount` (must be ≤ `deployed_to_yield`, else `RecallExceedsDeployed`), so the pro-rata redeem haircut then measures true CR and socializes the loss across all holders. Gated on `market.authority` (a governance action; it can only ever reduce reported backing, never inflate it). No token movement — a pure ledger correction.

#### `initialize_market(asset_symbol, oracle_type, initial_l_price, initial_s_price, mint_fee_bps, redeem_fee_bps, keeper_authority)`

Create a new market. Allocates `Market` PDA, creates L and S SPL mints (mint authority = market PDA), creates the collateral vault.

| Argument | Type | Notes |
| - | - | - |
| `asset_symbol` | String | ≤ 10 chars |
| `oracle_type` | enum | `Pyth` / `Hermes` / `Switchboard` / `Manual` |
| `initial_l_price` / `initial_s_price` | u64 | 6-decimal cUSDC. Constant-product anchor |
| `mint_fee_bps` / `redeem_fee_bps` | u16 | Max 1000 (10%) |
| `keeper_authority` | Pubkey | Initial keeper signer pubkey |

#### `update_market(mint_fee_bps?, redeem_fee_bps?, is_active?, dev_tax_pct?)`

Admin updates to fees, the active flag, and the dev-tax slice (≤ 25%). This is also the channel for the keeper's vol-informed fee posting - see [Fees](#fees).

#### `update_keeper_authority(new_keeper)` / `update_market_oracle(oracle_type)` / `update_fee_recipient(new_recipient)`

Rotate the keeper signer, repoint the oracle, or reroute fee flow. Bound to `market.authority`.

#### `withdraw_excess_to_clp(amount: u64)`

Drain collateral *above the market's dynamic target ratio* to the per-market CLP vault - the inverse of `donate_to_vault`. Guards: fresh TWAP required, only liquid (non-yield-deployed) collateral, and the post-withdrawal ratio must stay at or above `target_ratio_bps()`.

#### `configure_yield_ceiling(ceiling_bps?, enabled?)`

Enable collateral-yield management and set the deployable fraction (max 90%).

#### `configure_worst_case_quoting(enabled, normal_mult, proxy_mult, stress_mult)`

Configure per-state confidence multipliers used in mint pricing markup.

#### `configure_risk_fees`

Per-state fee adjustments (rare; defaults are usually fine).

#### `enable_multi_collateral`, `add_collateral_vault`, `update_collateral_vault`

Multi-collateral mode (accept multiple stablecoin/equity tokens as collateral with weights). Off for most markets; enabled per-market by admin if needed.

## Errors

| Code | Name | Cause |
| - | - | - |
| 6000 | `MarketNotActive` | `is_active = false` |
| 6001 | `InvalidAmount` | Zero or negative |
| 6002 | `InsufficientCollateral` | Vault can't cover redeem |
| 6003 | `InvalidOracle` | Oracle account mismatch |
| 6004 | `UnsupportedOracleType` | Bad `oracle_type` value |
| 6005 | `OraclePriceUnavailable` | Oracle paused or missing |
| 6006 | `MathOverflow` | Should be unreachable |
| 6007 | `SymbolTooLong` | Symbol > 16 chars |
| 6008 | `FeeTooHigh` | Fee bps > 1000 (10%) |
| 6009 | `MintNotAllowedInStress` | Risk state is `Stress` |
| 6010 | `RedeemNotAllowedInStress` | Reserved (redeem in `Stress` is currently allowed) |
| 6011 | `ExceedsStressMintCap` | Risk state is `ProxyMode`/`Recovery` and amount > throttled cap |
| 6012 | `TwapNotAvailable` | TWAP buffer not yet populated |
| 6013 | `AlreadyEnabled` | Multi-collateral already on |
| 6014 | `MultiCollateralNotEnabled` | Tried to add collateral type without enabling |
| 6015 | `MaxCollateralTypesExceeded` | More than 5 collateral types |
| 6016 | `InvalidWeight` | Multi-collateral weight not in \[1, 10000] bps |
| 6017 | `CollateralAlreadyExists` | Duplicate collateral mint |
| 6018 | `CollateralNotFound` | Unknown collateral type |
| 6019 | `BelowMinimum` | Mint \< 10 cUSDC |
| 6020 | `UnauthorizedKeeper` | Signer ≠ `market.keeper_authority` |
| 6021 | `Unauthorized` | Signer ≠ `market.authority` |
| 6022 | `InsufficientLiquidCollateral` | Yield deployed; recall first |
| 6023 | `ExceedsYieldCeiling` | Deployment > `yield_ceiling_bps` |
| 6024 | `YieldManagementDisabled` | `yield_enabled = false` |
| 6025 | `RecallExceedsDeployed` | Recall > deployed balance |

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

## TypeScript

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

const program = new Program(idl, provider);
const [marketPDA] = PublicKey.findProgramAddressSync(
  [Buffer.from("market"), Buffer.from("QQQ")],
  program.programId,
);

const market = await program.account.market.fetch(marketPDA);
console.log("L mint:", market.longMint.toBase58());
console.log("Risk:", Object.keys(market.riskState)[0]);
```

→ [Full mint+redeem example](/build/example-mint-redeem)

## See also

<CardGroup cols={2}>
  <Card title="Mint flow" icon="circle-plus" href="/flows/mint">
    Step-by-step `mint_paired` walkthrough.
  </Card>

  <Card title="Redeem flow" icon="arrow-rotate-left" href="/flows/redeem">
    Step-by-step `redeem_paired` walkthrough.
  </Card>
</CardGroup>
