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

# Stellar lending interest and revenue

> How supply and borrow APY accrue on XOXNO's Stellar lending: scaled balances and indexes, the two-kink interest-rate model, utilization, the reserve-factor split, rewards, and bad-debt socialization.

How borrower interest accrues, where protocol revenue goes, and how the pool socializes bad debt. Driven by `supply_index_ray` and `borrow_index_ray`. **Read first:** [Markets](/stellar-lending/dev/markets).

The pool never iterates accounts. It stores supply and debt as **scaled** amounts and applies interest to everyone at once by advancing two indexes, `supply_index_ray` and `borrow_index_ray`. A position's actual balance is always derived from its scaled amount and the current index.

## Scaled balances

Every supply and debt amount is stored as a scaled RAY value. The actual amount is the scaled value multiplied by the relevant index:

```text theme={"system"}
amount = scaled_amount_ray · index_ray / RAY
```

Supply positions and totals use `supply_index_ray`; debt positions and totals use `borrow_index_ray`. RAY is `10^27`. Both indexes start at `RAY` (`1.0`) when a market is created and only ever rise from interest — except the supply index, which a bad-debt cleanup can lower (see below). Views reconstruct actual supplied and borrowed amounts on demand from the current index and the asset's decimals.

## Utilization

Utilization is the fraction of supplied liquidity that is currently borrowed, in RAY:

```text theme={"system"}
U = borrowed_actual / supplied_actual          [RAY]
```

When supplied is zero, utilization is defined as zero (`utilization`, `common/src/rates.rs:142`). Utilization drives the borrow rate and, through it, the supply rate.

## Borrow-rate model

The annual borrow rate is a piecewise-linear curve with **two kinks** — `mid_utilization` and `optimal_utilization` — producing **three regions**. It starts at `base_borrow_rate` and adds `slope1`, then `slope2`, then `slope3` as utilization climbs (`calculate_borrow_rate`, `common/src/rates.rs:9`):

```text theme={"system"}
U < mid:            base + slope1 · U / mid
mid ≤ U < optimal:  base + slope1 + slope2 · (U − mid) / (optimal − mid)
U ≥ optimal:        base + slope1 + slope2 + slope3 · (U − optimal) / (1 − optimal)
rate = min(rate, max_borrow_rate)
```

The final rate is capped at `max_borrow_rate` (`≤ 2·RAY`). Parameters must satisfy `base ≤ slope1 ≤ slope2 ≤ slope3 ≤ max` and `0 < mid < optimal < 1 RAY`, so the curve is non-decreasing.

<Warning>
  `max_utilization` is a **separate hard cap, not a rate kink**. When a borrow or withdraw would push post-state utilization above `max_utilization`, the call reverts with `#127 UtilizationAboveMax` (unless `max_utilization ≥ 1 RAY`). It bounds how much of the pool can be borrowed; it does not change the rate curve. On every current mainnet market `max_utilization = 95%`, while `optimal` is `80%` (or `65%` for BTC and ETH).
</Warning>

## Accrual

On every mutating pool call and on `update_indexes`, the pool advances the borrow index by compounding the rate over the elapsed time. Compounding is **per millisecond**, accrued in chunks of at most one year, using an 8-term Taylor approximation of `e^x` (`compound_interest`, `common/src/rates.rs:59`):

```text theme={"system"}
new_borrow_index = old_borrow_index · e^(rate_per_ms · Δt_ms)
```

The borrow index is capped at `1e36` (`MAX_BORROW_INDEX_RAY`, `common/src/constants/pool.rs:11`); exceeding it raises `#33 MathOverflow`. The Taylor remainder is bounded because each per-chunk exponent is small.

## Splitting interest

The interest accrued on outstanding debt is split by `reserve_factor_bps` into a protocol fee and supplier rewards (`calculate_supplier_rewards`, `common/src/rates.rs:123`):

```text theme={"system"}
accrued_interest = borrowed · (new_borrow_index − old_borrow_index)
protocol_fee     = reserve_factor_bps / BPS · accrued_interest
supplier_rewards = accrued_interest − protocol_fee
```

The supplier share lifts `supply_index`, so every supplier's position appreciates without any per-account write. The supply index is floored at `WAD` (`SUPPLY_INDEX_FLOOR_RAW = WAD`, `common/src/constants/pool.rs:4`); if it is already at or below that floor, the reward step is skipped. The protocol fee is added to `revenue_ray` and held as a scaled supply claim.

The **display supply APR** follows directly (`calculate_deposit_rate`, `common/src/rates.rs:37`):

```text theme={"system"}
supply_apr = utilization · borrow_rate · (1 − reserve_factor_bps / BPS)
```

Worked example: at `U = 80%` (`0.8e27`), a borrow rate of `5%` (`0.05e27`), and a reserve factor of `1000` BPS (10%), the supply APR is `0.8 · 0.05 · 0.9 = 0.036` — a `3.6%` supply rate against a `5%` borrow rate.

## Protocol revenue

Protocol revenue is not a separate token balance. It is held as a scaled supply claim in `revenue_ray`, bounded by total supply (`0 ≤ revenue_ray ≤ supplied_ray`), so it appreciates with the supply index until claimed. On `claim_revenue` the pool syncs indexes, reconstructs the claimable amount, caps it by live reserves, burns the proportional scaled revenue, and transfers to its owner — the controller — which forwards it to the configured accumulator. See [Controller ABI](/stellar-lending/dev/controller-abi).

## Rewards

`add_rewards(asset, amount)` lifts `supply_index` so every supplier benefits proportionally, exactly as the supplier-interest share does. It raises `#37 NoSuppliersToReward` if the market has no supply, because there is no scaled base to distribute against.

## Bad debt

When an account's total collateral is `≤ $5 WAD` (`BAD_DEBT_USD_THRESHOLD = 5·WAD`) and its debt exceeds that collateral, the position is unrecoverable. Cleanup — inline during a liquidation, or via the keeper's `clean_bad_debt` — seizes all of the account's supply and debt, removes the account, and **socializes the shortfall** by lowering each affected market's supply index proportionally:

```text theme={"system"}
new_supply_index = supply_index · (1 − capped_bad_debt / total_supplied_value)
```

The result is floored at `WAD` (`SUPPLY_INDEX_FLOOR_RAW`), so the index can never reach zero and divide-by-near-zero is impossible. The shortfall is shared across that market's suppliers; the event `CleanBadDebtEvent` records the cleared totals. See [Liquidations](/stellar-lending/dev/liquidations).

## Fixed-point units

| Unit | Raw      | Decimals | Used for                                                              |
| ---- | -------- | -------- | --------------------------------------------------------------------- |
| BPS  | `10_000` | 4        | Reserve factor, LTV, liquidation threshold / bonus / fee, tolerances. |
| WAD  | `10^18`  | 18       | USD prices, USD position values, health factor, dust floors.          |
| RAY  | `10^27`  | 27       | Indexes, utilization, rates, scaled supply and debt balances.         |

Multiply and divide helpers use half-up rounding by default, flooring where it protects the protocol and ceiling for amounts a user owes.

## Next

<CardGroup cols={2}>
  <Card title="Markets" icon="store" href="/stellar-lending/dev/markets">
    What a market is and how its rate model is configured.
  </Card>

  <Card title="Risk parameters" icon="sliders" href="/stellar-lending/dev/risk-parameters">
    Reserve factor, caps, dust floors, and the rate-model constraints.
  </Card>

  <Card title="Liquidations" icon="gavel" href="/stellar-lending/dev/liquidations">
    How bad debt is triggered and socialized into the supply index.
  </Card>
</CardGroup>
