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

# BondVault Redemption Throttle

> Per-epoch redemption cap (108 bps/week) + FIFO queue. Anti bond-run, anti death-spiral.

<Note>
  This page covers how a redemption is gated **after** the bond has matured.
  For the bond minting and 730-day maturity rules, see
  [Claim Bonds](/concepts/claimbonds).
</Note>

`BondVault` enforces a **per-epoch redemption throttle** so that no single
epoch can drain the vault's LUMINA reserve in one block, even if every
bond holder in that epoch tries to redeem simultaneously. The mechanism is
a fixed weekly cap (`MAX_REDEMPTION_PER_EPOCH_BPS = 108`) plus a FIFO
queue for the overflow.

## The number

| Constant                               | Value                                  |
| -------------------------------------- | -------------------------------------- |
| `MAX_REDEMPTION_PER_EPOCH_BPS`         | **`108`** (1.08% per epoch / per week) |
| Epoch length                           | **7 days** (one week per epoch)        |
| Worst-case drain time for a full epoch | **≈ 12 weeks** (12 × 1.08% ≈ 12.96%)   |

A single epoch's bond supply can therefore be drained, in the worst case,
across **about 12 consecutive weeks** — never instantly. The 1.08%/week
ceiling is the entire throttle: there's no separate per-wallet or
per-block cap layered on top.

## What it actually limits

The throttle limits the **LUMINA paid out** per epoch per week, expressed
as a fraction of that epoch's total minted face. Concretely, for an
epoch with `S` units of face value minted:

```
maxRedeemableThisWeek(epoch) = S × 108 / 10_000   // 1.08% of the epoch's face
```

Bonds redeemed at maturity within this allowance are paid **immediately**
in LUMINA. Bonds beyond the allowance are routed to the FIFO queue.

## FIFO queue — burn-now, deliver-later

When a redemption request exceeds the weekly throttle:

1. The bond tokens are **burned at queue time** (they leave the user's
   wallet immediately — there is no "pending bond" balance to
   double-spend).
2. A queue entry is created recording the holder, the USD amount, and
   the position in the FIFO line.
3. When `processQueue()` is called in a later epoch with available
   weekly allowance, the queue is drained in **first-in-first-out**
   order and LUMINA is delivered to the recorded holder addresses.

`processQueue()` is **public** — anyone can call it (a keeper, the
holder themselves, or an unrelated wallet). The function pays out as
many queue entries as the current week's remaining allowance permits,
then stops. In practice, a keeper bot calls it once per epoch.

## Why it exists

Two failure modes that the throttle defends against:

1. **Bond-run.** Without the throttle, a single big epoch (e.g. a
   correlated mass-trigger across all 6 shields during a crash) could let
   bond holders redeem every token at once, dumping the entire LUMINA
   reserve onto the open market in one block. The throttle spreads that
   sell pressure over weeks.
2. **Death spiral.** If a flash drop both **triggers** bonds (good) and
   **simultaneously drops the LUMINA/USD reference** (bad), unbounded
   redemption would mint disproportionately more LUMINA per bond, which
   in turn pushes LUMINA down further, which mints even more on the next
   redemption, and so on. The throttle slows this feedback loop enough
   for the buyback / TWAP burn machinery to catch up.

## Practical effects for agents

* **Small redemptions are immediate.** If you're redeeming 1% of an
  epoch's face or less and you're early in the week, you get paid in
  the same transaction.
* **Big redemptions are queued.** You'll receive a `RedemptionQueued`
  event instead of `BondRedeemed`. Your bonds are gone; LUMINA arrives
  later via `RedemptionProcessed` events.
* **Listen, don't poll.** Use webhooks (`bond_redeemed`,
  `redemption_processed`) instead of polling `BondVault` state — the
  exact epoch when your queue entry pays is data-dependent.
* **Anyone can `processQueue()`.** If you're tired of waiting and the
  current epoch has unused allowance, you can call `processQueue()`
  yourself.

## Reading throttle state

```bash theme={null}
# Total face minted in epoch N:
cast call $BOND_VAULT 'epochSupply(uint256)(uint256)' $EPOCH --rpc-url $BASE_MAINNET_RPC

# Remaining redemption allowance this week for epoch N:
cast call $BOND_VAULT 'remainingAllowance(uint256)(uint256)' $EPOCH --rpc-url $BASE_MAINNET_RPC

# Queue length for epoch N:
cast call $BOND_VAULT 'queueLength(uint256)(uint256)' $EPOCH --rpc-url $BASE_MAINNET_RPC
```

## Worked example

An epoch mints `1,000,000` units of face (`$1M` notional).

| Week | Max redeemable | Cumulative |
| ---- | -------------- | ---------- |
| 1    | `10,800` units | 1.08%      |
| 2    | `10,800`       | 2.16%      |
| 3    | `10,800`       | 3.24%      |
| …    | …              | …          |
| 12   | `10,800`       | 12.96%     |

After 12 weeks, **≈ 13%** of the epoch's face has been redeemed in the
worst case. Anything beyond that continues at 1.08%/week into subsequent
epochs. The protocol does not promise any redemption schedule faster than
this.

## Check Redemption Status Programmatically

The on-chain getters above (`epochSupply`, `remainingAllowance`,
`queueLength`) tell you the **aggregate** state of an epoch. To answer
the holder-centric question — *"will my specific redemption clear now or
get queued, and if queued, when?"* — use the SDK, which composes those
calls with the new throttle-dynamic views (`availableCapacityRatioBps`,
`policiesPaused`) into a single response.

Requires `@lumina-org/sdk` v0.7.0+.

```typescript theme={null}
import { BondQueue, bondVaultAbi } from '@lumina-org/sdk';
import { ethers } from 'ethers';

// getRedemptionStatus lives on the BondQueue helper (NOT lumina.bonds).
const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const bv = new ethers.Contract(bondVaultAddress, bondVaultAbi, provider);
const queue = new BondQueue(bv);

const status = await queue.getRedemptionStatus(
  holderAddress,
  epochIdBond,
  usdAmount,
);
console.log(status);
// {
//   status: 'queued',
//   estimatedReleaseDate: '2026-08-15T00:00:00.000Z',
//   queuePosition: 47,
//   throttleInfo: {
//     weeklyCapBps: 108,
//     weeklyCapUsd: '756000',
//     usedThisEpochUsd: '...',
//     remainingThisEpochUsd: '...',
//     nextEpochStart: '...'
//   },
//   policiesPaused: false,
//   availableCapacityBps: 8500
// }
```

The `status` field is one of:

* **`immediate`** — the entire `usdAmount` fits inside the current
  week's remaining allowance; calling `redeem()` now pays out LUMINA in
  the same transaction.
* **`queued`** — the request exceeds remaining allowance; bonds will be
  burned and a FIFO entry created. Use `estimatedReleaseDate` and
  `queuePosition` to set holder expectations.
* **`partial`** — part of `usdAmount` fits in the current week, the
  rest queues. The SDK splits the response so you can show both halves
  in the UI.

Bonds are tracked per **`(holder, epochIdBond)`** pair — not per
individual `bondId` — because [Claim Bonds](/concepts/claimbonds) are
ERC-1155 fungible **within an epoch**. Two holders holding the same
epoch share one supply curve but are queued independently.

## Auto-injection Mechanism

When BondVault's available-capacity ratio drops to **50% or below**,
BondVault automatically pulls **10% of the CEX Reserve's LUMINA
balance** into the vault on the next `redeem`, `issue`, or
`processQueue` call. This widens the safety margin without requiring a
manual rebalance.

The injection is bounded by the actual CEX Reserve balance, so once
that's depleted the mechanism becomes a no-op until ops refills the
reserve.

```mermaid theme={null}
flowchart LR
  A[redeem / issue / processQueue] --> B{available capacity ≤ 50%?}
  B -->|yes| C[pull 10% of CEX Reserve balance into vault]
  B -->|no| D[skip]
  C --> E[emit AutoInjectionTriggered]
```

This is **on-chain only** — no off-chain keeper required. Every
state-changing call that touches BondVault evaluates the trigger,
emitting `AutoInjectionTriggered(uint256 amountInjected, uint256
newCapacityBps)` when it fires.

## Floor-Price Soft Pause

If the LUMINA spot price drops at-or-below **\$0.005**, BondVault flips
`policiesPaused = true`. This is a **signal** that the SDK and frontend
should consume to refuse new policy purchases — the contract does
**not** enforce it inside [CoverRouterV2](/concepts/shields), so any
integration that bypasses the SDK can still submit purchases on-chain.

Recovery requires the price to rise back to **120% of floor (\$0.006)** —
the hysteresis band avoids flapping at the boundary.

The SDK exposes this via `status.policiesPaused` (see the snippet
above), and any front-end calling `getRedemptionStatus` for UX checks
gets the flag for free.

## Related audit fixes

* **FIX C-3 + H-6** — solvency floor and LUMINA/USD snapshot alignment;
  the throttle does **not** override these — it sits on top of them.
* **FIX M-11** — `BondVault.burnFromReserves` 5%-per-tx burn cap (the
  reserve guard the throttle complements).
* **Sprint Fix Audit Economic** — the `availableCapacityRatioBps()` /
  `policiesPaused()` views, the auto-injection trigger, and the SDK
  `getRedemptionStatus()` composition described above.

## See also

* [Claim Bonds](/concepts/claimbonds) — the ERC-1155 token being redeemed.
* [LUMINA token](/concepts/lumina-token) — what gets paid out.
* [Lifecycle](/concepts/lifecycle) — where the throttle sits in the full flow.
