> ## 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 health factor

> How the controller weights collateral by liquidation threshold against debt to decide whether an account is solvent or liquidatable.

The single number the controller uses to decide whether an account is solvent, may borrow, or can be liquidated. **Read first:** [Risk parameters](/stellar-lending/dev/risk-parameters).

## Definition

Health factor (HF) compares an account's liquidation-threshold-weighted collateral against its total debt, both valued in USD-WAD (`10^18`):

```text theme={"system"}
HF = liquidation_threshold_weighted_collateral_usd / total_debt_usd
```

The controller computes it in `calculate_health_factor` (`contracts/controller/src/helpers/math.rs:49`). The division floors (`div_floor`), so rounding always reports the more conservative value. An account with **no debt** returns `i128::MAX` — an effectively infinite health factor. An account is **liquidatable when `HF < 1e18`** (one WAD).

Each term is built from a scaled position, its market index, and the asset price:

```text theme={"system"}
position_value_usd = to_wad(scaled_amount_ray · index_ray) · price_wad        [WAD]
weighted_collateral = Σ position_value(supply_i) · liquidation_threshold_i / BPS
total_debt          = Σ position_value(borrow_i)
```

The liquidation threshold applied to each collateral is the value **snapshotted onto that position at supply time** (`AccountPositionRaw`) from the account's spoke asset config. The health factor is always evaluated post-execution on cached indexes, so a borrow or withdraw is checked against the state it would produce.

<Note>
  Health factor is **separate from the LTV gate**. Borrow and withdraw additionally require `Σ position_value(supply_i) · loan_to_value_i / BPS ≥ total_debt_usd`. Because LTV is always below the liquidation threshold, an account reaches its borrow limit before it becomes liquidatable.
</Note>

## Worked example

An account supplies $1,000 of collateral whose liquidation threshold is `0.78` (`7800` BPS) and borrows $500. All USD values are WAD-scaled, so $1,000 is `1000 · 10^18 = 1000e18` and $500 is `500e18`.

```text theme={"system"}
weighted_collateral = 1000e18 · 7800 / 10000 = 780e18      ($780)
total_debt          = 500e18                                ($500)
HF                  = 780e18 / 500e18 = 1.56e18             (1.56)
```

The health factor is `1.56e18`, comfortably above the `1e18` boundary, so the account is solvent and not liquidatable. If the collateral price halved, `weighted_collateral` would fall to `390e18` and HF to `0.78e18` — below `1e18`, and the account would become liquidatable. Recompute after any supply, borrow, repay, withdraw, price change, or liquidation.

## Decision table

| Condition                  | Meaning              | What it allows                                                    |
| -------------------------- | -------------------- | ----------------------------------------------------------------- |
| `HF ≥ 1e18`                | Solvent.             | Borrow or withdraw, subject to the LTV gate.                      |
| `HF < 1e18`                | Liquidatable.        | A liquidator may repay eligible debt and seize collateral.        |
| No debt (`HF = i128::MAX`) | No borrow-side risk. | Withdraw freely; supply and borrow checks still apply per market. |

## Edge cases

| Case                                    | Behavior                                                                                                                                                                                    |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Account has no debt                     | `calculate_health_factor` returns `i128::MAX`; treat as unbounded, never as a finite large number.                                                                                          |
| Price unavailable, risk-increasing flow | The oracle is fail-closed: a missing primary price under a strict policy reverts the whole operation rather than pricing the position at zero. See [Oracles](/stellar-lending/dev/oracles). |
| Near-boundary rounding                  | The floor division and integer math can flip an account just over `1e18` to just under after a price tick; keep a buffer in any UI or simulation.                                           |
| Dust positions                          | A partial withdraw or repay leaving sub-floor residue is expanded to a full close before HF is finalized; see [Risk parameters](/stellar-lending/dev/risk-parameters#constraints).          |

<Warning>
  On-chain health-factor values are **WAD-scaled** (`10^18`). The view `health_factor(account_id) -> i128` returns this raw WAD integer, and `i128::MAX` when the account is debt-free. Divide by `10^18` only for display; never approve a borrow or withdraw from cached account or price data — re-read state before building the transaction.
</Warning>

## Next

<CardGroup cols={2}>
  <Card title="Liquidations" icon="gavel" href="/stellar-lending/dev/liquidations">
    What happens once an account crosses below `HF = 1`.
  </Card>

  <Card title="Risk parameters" icon="sliders" href="/stellar-lending/dev/risk-parameters">
    The liquidation thresholds and LTVs that weight the formula.
  </Card>

  <Card title="Accounts and risk" icon="user-shield" href="/stellar-lending/dev/accounts-and-risk">
    Position snapshotting, spoke risk, and risk gates.
  </Card>

  <Card title="Oracles" icon="tower-broadcast" href="/stellar-lending/dev/oracles">
    The prices that feed every position value.
  </Card>
</CardGroup>
