> ## 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 paired tokens

> Deposit cUSDC, receive matched long and short tokens at NAV.

`mint_paired(collateral_amount)` is the canonical user-facing entry. It deposits cUSDC and mints L and S to the user's ATAs.

## Math

```
fee         = collateral_amount × mint_fee_bps / 10_000
half_value  = (collateral_amount - fee) / 2
qty_L       = half_value / L_NAV
qty_S       = half_value / S_NAV

L_NAV       = market.user_twap_price (or initial_l_price if TWAP == 0)
S_NAV       = (initial_l × initial_s) / L_NAV
```

In `Normal` risk state with no confidence markup, `qty_L × L_NAV + qty_S × S_NAV = collateral_amount - fee`. Trivially.

## Fees

The base fee is **10bps** (`mint_fee_bps`). Two things about it are easy to miss:

* **90% of the fee stays in the collateral vault.** Only the dev tax (`dev_tax_pct = 10%` of the fee) goes to the fee recipient. The retained portion is real backing - it accrues directly to the market's collateralization ratio.
* **On high-vol markets the fee is vol-informed.** The keeper posts `fee = max(10, fee_mult × q90)` bps, capped at 100, where q90 is the trailing realized spread of the market's oracle moves. The fee is the hurdle for paired round-trip extraction - scaling it with realized vol is what keeps high-vol listings solvent (see [Solvency](/concepts/solvency)). Calm broad indices stay at the static 10bps.

Risk-state discounts and [fee waivers](#optional-mint_paired_with_waiver) apply on top.

## Oracle staleness gate

Mint requires a TWAP that is fresher than **300 seconds** (`MAX_TWAP_AGE_SECS`) when the market is not in `Normal` risk state - which is always the case on devnet (ProxyMode). When the underlying market closes (nights, weekends), the keeper deliberately **withholds** oracle pushes; the TWAP ages past the gate and mint/redeem freezes itself with `TwapNotAvailable` until the market reopens. This is intentional: minting at a stale Friday close into a Monday gap would be free money against the vault. The pool books keep floating off-hours - see [Trade](/flows/trade).

## Accounts required

| Account | Notes |
| - | - |
| `market` | Market PDA |
| `user` (signer) | Pays `collateral_amount` |
| `user_collateral` | User's cUSDC ATA (must exist; create via `getAssociatedTokenAddressSync` + ATA program if missing) |
| `user_long` | User's L ATA (created if missing) |
| `user_short` | User's S ATA (created if missing) |
| `long_mint` | From `market.long_mint` |
| `short_mint` | From `market.short_mint` |
| `collateral_vault` | From `market.collateral_vault` |
| `dev_token_account` | Fee recipient's cUSDC ATA |
| `oracle_address` | From `market.oracle_address` |
| `token_program` | SPL Token program |

The frontend's `useMint()` hook resolves all of this from the symbol. For a copy-pasteable script see [Build → Mint and redeem example](/build/example-mint-redeem).

## TypeScript

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

const program = new Program(idl, provider);
const symbol = "QQQ";

// Derive market PDA
const [marketPDA] = PublicKey.findProgramAddressSync(
  [Buffer.from("market"), Buffer.from(symbol)],
  program.programId,
);

// Fetch market for child accounts
const market = await program.account.market.fetch(marketPDA);

// User ATAs
const userCusdc = getAssociatedTokenAddressSync(market.collateralMint, wallet.publicKey);
const userLong  = getAssociatedTokenAddressSync(market.longMint, wallet.publicKey);
const userShort = getAssociatedTokenAddressSync(market.shortMint, wallet.publicKey);
const devCusdc  = getAssociatedTokenAddressSync(market.collateralMint, market.feeRecipient);

// Build the tx
const amount = new BN(100_000_000); // 100 cUSDC

const ix = await program.methods
  .mintPaired(amount)
  .accounts({
    market: marketPDA,
    user: wallet.publicKey,
    userCollateral: userCusdc,
    userLong: userLong,
    userShort: userShort,
    longMint: market.longMint,
    shortMint: market.shortMint,
    collateralVault: market.collateralVault,
    devTokenAccount: devCusdc,
    oracleAddress: market.oracleAddress,
    tokenProgram: TOKEN_PROGRAM_ID,
  })
  .instruction();

// You may need to prepend ATA creation instructions if longATA/shortATA don't exist yet:
const preIxs = [];
const longInfo = await connection.getAccountInfo(userLong);
if (!longInfo) {
  preIxs.push(
    createAssociatedTokenAccountInstruction(
      wallet.publicKey,
      userLong,
      wallet.publicKey,
      market.longMint,
    ),
  );
}
// (do the same for userShort)

const tx = new Transaction().add(...preIxs, ix);
const sig = await provider.sendAndConfirm(tx);
```

## Minimum mint size

Enforced by the program: **10 cUSDC** (`10_000_000` lamports). Below this, you get `BelowMinimum`.

This exists to keep the post-fee residual computable without precision issues - at 1 cUSDC mint, the 0.1% fee rounds to lamport boundaries that produce zero L/S in some markets.

## Errors

| Error | Cause |
| - | - |
| `MarketNotActive` | `market.is_active = false` |
| `InvalidAmount` | `collateral_amount = 0` |
| `BelowMinimum` | Less than 10 cUSDC |
| `InsufficientCollateral` | User's cUSDC ATA balance \< `collateral_amount` |
| `OraclePriceUnavailable` | Oracle account stale or paused |
| `TwapNotAvailable` | TWAP older than 300s - typically the underlying market is closed and the keeper is withholding pushes |
| `MintNotAllowedInStress` | Risk state is `Stress` |
| `ExceedsStressMintCap` | Risk state is `ProxyMode` / `Recovery` and amount exceeds the throttled cap |
| `OICapExceeded` | `total_l_supply + l_to_mint > oi_cap` |
| `MathOverflow` | Should be unreachable; report if you see this |

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

## Behavior across risk states

| Risk state | Behavior |
| - | - |
| `Normal` | Mint at NAV, no markup, no size throttle. |
| `ProxyMode` | Mint with 2× confidence markup, size-throttled. |
| `Stress` | Mint **rejected**. |
| `Recovery` | Mint at NAV, size-throttled. |

→ [Risk states](/concepts/risk-states)

## Optional: `mint_paired_with_waiver`

A variant that consumes a fee-waiver against the user's `FeeWaiver` PDA, if active. Saves the mint fee but only works when the user has previously called `donate_to_vault` to acquire a waiver and the market vault is at or above its dynamic buffer target (keeper-posted per market, floored at 102%).

This is an advanced flow used by power users. Most integrations stick with `mint_paired`.

## What you receive

After a successful mint:

```
dev_tax = fee × dev_tax_pct / 100        // 10% of the fee

User cUSDC ATA: -collateral_amount
Vault cUSDC:    +(collateral_amount - dev_tax)   // retained fee stays as backing
Dev cUSDC ATA:  +dev_tax
User L ATA:     +qty_L
User S ATA:     +qty_S

market.total_l_supply: += qty_L
market.total_s_supply: += qty_S
market.total_collateral: += (collateral_amount - dev_tax)
```

The L and S tokens are **SPL tokens**. They are transferable, tradeable on the [native venues](/flows/trade), usable in CPI by other programs. There's no special locking, lockup, or hold period.

## See also

<CardGroup cols={2}>
  <Card title="Redeem" icon="arrow-rotate-left" href="/flows/redeem">
    The inverse: burn paired tokens, receive cUSDC at NAV.
  </Card>

  <Card title="End-to-end TypeScript example" icon="terminal" href="/build/example-mint-redeem">
    Copy-pasteable script with wallet setup, IDL load, mint, verify.
  </Card>
</CardGroup>
