> For the complete documentation index, see [llms.txt](https://docs.bsc.lista.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bsc.lista.org/for-developer/lista-lending/irm.md).

# Interest Rate Model (IRM)

Moolah markets delegate borrow-rate computation to a pluggable Interest Rate Model (IRM). Each market stores an `irm` address, and on every interest accrual Moolah calls that contract to obtain the current borrow rate.

Two IRM implementations ship with the protocol:

| IRM                 | Model                                                                                         | Where used                                                                                                                 |
| ------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `InterestRateModel` | Adaptive-curve rate as a function of utilization, with a rate-at-target that adapts over time | Standard utilization-based markets                                                                                         |
| `FixedRateIrm`      | Administratively set fixed per-market rate                                                    | Fixed term / fixed rate broker products (see [Integration Patterns](/for-developer/lista-lending/integration-patterns.md)) |

Every IRM implements the shared `IIrm` interface:

```solidity
interface IIrm {
    /// @notice Borrow rate per second (scaled by WAD); may modify storage.
    function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256);

    /// @notice Borrow rate per second (scaled by WAD); read-only.
    function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256);
}
```

All rates in the IRM are expressed **per second, scaled by WAD (`1e18`)**. Moolah compounds this per-second rate over the elapsed time between interactions when accruing interest.

## Adaptive Curve Model

`InterestRateModel` computes the borrow rate as a curve function of utilization around a target utilization. The curve is anchored by a `rateAtTarget` value that itself adapts over time: when utilization sits above target, `rateAtTarget` drifts upward; when below target, it drifts downward. This pushes the market back toward the target utilization without governance intervention.

### Utilization and error

Utilization is total borrows divided by total supply:

```
utilization = totalBorrowAssets / totalSupplyAssets   (WAD-scaled, 0 if no supply)
```

The model measures how far utilization is from target as a normalized error `err`, mapped so that `err = -1` at zero utilization, `err = 0` at target utilization, and `err = +1` at full (100%) utilization:

```
errNormFactor = utilization > TARGET_UTILIZATION ? (WAD - TARGET_UTILIZATION) : TARGET_UTILIZATION
err           = (utilization - TARGET_UTILIZATION) / errNormFactor
```

### The curve

Given the current `rateAtTarget` and the error, the instantaneous rate is a piecewise-linear curve. Below target the rate is scaled down toward `rateAtTarget / CURVE_STEEPNESS`; above target it is scaled up toward `rateAtTarget * CURVE_STEEPNESS`:

```
r = ((1 - 1/C) * err + 1) * rateAtTarget    if err < 0
    ((C - 1)   * err + 1) * rateAtTarget    if err >= 0
```

where `C = CURVE_STEEPNESS`. At `err = 0` (utilization exactly at target) the borrow rate equals `rateAtTarget`.

### Adaptation of `rateAtTarget`

`rateAtTarget` is stored per market (`mapping(Id => int256) rateAtTarget`) and is updated only on stateful interactions. Between two updates it moves according to `ADJUSTMENT_SPEED * err` per second, applied through a continuous-compounding (exponential) adaptation over the elapsed time:

```
linearAdaptation = (ADJUSTMENT_SPEED * err) * elapsed
endRateAtTarget  = clamp(startRateAtTarget * exp(linearAdaptation), MIN_RATE_AT_TARGET, MAX_RATE_AT_TARGET)

# then bounded again by the market's own cap / floor before being stored:
endRateAtTarget  = clamp(endRateAtTarget, rateFloor * 4, cap / 4)
```

The second clamp is applied to the value that is actually persisted, so after a `rateCap` or `rateFloor` change the stored `rateAtTarget` is bounded to `[rateFloor * 4, cap / 4]` — not only to the protocol-wide `MIN_RATE_AT_TARGET` / `MAX_RATE_AT_TARGET`. The factor of 4 is `CURVE_STEEPNESS`, so the bound expresses the cap/floor in terms of the curve endpoints. If `rateFloor * 4` exceeds `cap / 4`, the cap side wins.

The average rate returned to Moolah over the elapsed interval is approximated with a trapezoidal rule (using the start, mid, and end `rateAtTarget`), so the reported rate reflects the whole interval rather than only its endpoints. On a market's first interaction (`rateAtTarget == 0`), the model seeds both the average and end rate-at-target with `INITIAL_RATE_AT_TARGET`.

The resulting average rate is additionally bounded by a per-market cap and floor (`rateCap`, `rateFloor`) and a protocol-wide minimum cap (`minCap`); when no explicit cap is set, `DEFAULT_RATE_CAP` applies. These bounds are manager/bot-adjustable on-chain values.

Changing either bound settles first: `updateRateCap` and `updateRateFloor` (on both IRMs, and `setBorrowRate` on `FixedRateIrm`) call `Moolah.accrueInterest` for the market before writing the new value, so interest accrued under the old bound is booked at the old bound. The call is a no-op for a market that has never been touched (`lastUpdate == 0`).

## Adaptive-curve constants

These are compiled into `ConstantsLib` and inlined, so there is no getter to read them from — changing one requires a contract upgrade. All rate constants are **per second, WAD-scaled**; the percentages in *Meaning* are those same values expressed per year.

| Constant                 | On-chain value                  | Meaning                                                                                                       |
| ------------------------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `CURVE_STEEPNESS`        | `4 ether` (`4e18`)              | Curve steepness `C = 4`. Rate ranges from `rateAtTarget / 4` at 0% utilization to `rateAtTarget * 4` at 100%. |
| `TARGET_UTILIZATION`     | `0.9 ether` (`0.9e18`)          | Target utilization = 90%.                                                                                     |
| `ADJUSTMENT_SPEED`       | `50 ether / 365 days`           | Adaptation speed; `rateAtTarget` moves at `ADJUSTMENT_SPEED * err` per second (≈ 50 / year at full error).    |
| `INITIAL_RATE_AT_TARGET` | `0.04 ether / 365 days`         | Seed rate-at-target on first interaction; 4% APR-equivalent at target (rate spans \~1%–16% across the curve). |
| `MIN_RATE_AT_TARGET`     | `0.001 ether / 365 days`        | Lower clamp on `rateAtTarget`; 0.1% at target (curve minimum \~0.025%).                                       |
| `MAX_RATE_AT_TARGET`     | `2.0 ether / 365 days`          | Upper clamp on `rateAtTarget`; 200% at target (curve maximum \~800%).                                         |
| `DEFAULT_RATE_CAP`       | `uint256(0.3 ether) / 365 days` | Default per-second cap on the returned borrow rate (30% annualized) when a market has no explicit `rateCap`.  |

## `borrowRateView` vs `borrowRate`

Both entry points evaluate the same adaptive-curve math, but differ in whether they persist the updated `rateAtTarget`:

|            | `borrowRateView`                                                            | `borrowRate`                                                                             |
| ---------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| Mutability | `view` (no state change)                                                    | State-changing                                                                           |
| Caller     | Anyone                                                                      | `MOOLAH` only (reverts otherwise)                                                        |
| Effect     | Computes the average rate for the current `market` snapshot without writing | Computes the rate, writes `rateAtTarget[id] = endRateAtTarget`, emits `BorrowRateUpdate` |
| Use case   | Off-chain quoting, simulations, front-ends                                  | Called by Moolah during interest accrual on `supply` / `borrow` / `repay` / etc.         |

Because `borrowRate` requires `msg.sender == MOOLAH`, integrators reading a rate directly should call `borrowRateView`. The value it returns is the average per-second rate that *would* be applied for the given `market` snapshot; it does not advance `rateAtTarget`. `borrowRateView` uses the stored `rateAtTarget` and the elapsed time implied by `market.lastUpdate`, so its result already reflects adaptation that has not been committed on-chain.

## Reading the current rate on-chain

Two complementary reads are available:

1. **The anchor rate.** `rateAtTarget(Id id)` returns the stored per-second rate-at-target for a market (the height of the curve). It is seeded to `INITIAL_RATE_AT_TARGET`, clamped by the market's floor and cap, in the **market-creation transaction** — `createMarket` calls `IIrm.borrowRate` once to initialize the model. A `0` reading therefore means this IRM has never been invoked for that `Id`: the market does not exist, or it uses a different IRM.

   ```solidity
   int256 anchor = IInterestRateModel(irm).rateAtTarget(id);
   ```
2. **The current borrow rate.** Fetch the market's live `MarketParams` and `Market` structs from Moolah, then call `borrowRateView`:

   ```solidity
   uint256 ratePerSecond = IIrm(irm).borrowRateView(marketParams, market); // WAD-scaled
   ```

### Per-second → APY conversion

The returned rate is a per-second rate scaled by WAD. Moolah accrues interest with continuous (per-second Taylor-compounded) growth, so the borrow APY is:

```typescript
const SECONDS_PER_YEAR = 365 * 24 * 60 * 60; // 31_536_000

// ratePerSecond is the WAD-scaled uint returned by borrowRateView
const rateFloat = Number(ratePerSecond) / 1e18;
const borrowAPY = Math.expm1(rateFloat * SECONDS_PER_YEAR); // e.g. 0.05 == 5%
```

The supply APY is lower than the borrow APY, scaled by utilization and net of the market fee; it is not returned by the IRM and must be derived from market state.

## FixedRateIrm

For products that do not use the utilization-based curve, brokers can point a market at `FixedRateIrm`. Instead of computing a rate from utilization, it returns a per-market rate that has been set administratively.

| Element                                      | Signature / value      | Notes                                                                                                                                                                                                                                                                                                                                   |
| -------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MAX_BORROW_RATE`                            | `8.0 ether / 365 days` | Maximum settable per-second rate (800% annualized).                                                                                                                                                                                                                                                                                     |
| `borrowRateStored(Id id)`                    | `int256`               | The fixed per-second rate stored for a market.                                                                                                                                                                                                                                                                                          |
| `setBorrowRate(Id id, int256 newBorrowRate)` | —                      | Sets the fixed rate. Access-controlled (bot role); must be `>= 0` and `<= MAX_BORROW_RATE`, and within any configured `rateCap` / `rateFloor`. Interest accrued under the previous rate is settled first (see below).                                                                                                                   |
| `borrowRateView(...)` / `borrowRate(...)`    | `uint256`              | Both return the stored rate (clamped to `MAX_BORROW_RATE`, then to the market cap and floor). Unlike the adaptive model, `borrowRate` here is also `view` and holds no adaptive state. A market whose rate has never been set has `borrowRateStored == 0`, so these return `0` (subject to any configured floor) rather than reverting. |

A `0` rate means no interest accrues, so treat an unset market as unconfigured rather than free. This IRM backs the fixed term / fixed rate broker products described in [Integration Patterns](/for-developer/lista-lending/integration-patterns.md).

## Related

* [Contract & Interface Reference](/for-developer/lista-lending/contract-reference.md) — `minLoan`, the global reentrancy guard and the rest of the Lista-specific controls.
* [Smart Contract](/for-developer/lista-lending/smart-contract.md) — deployed contract addresses.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.bsc.lista.org/for-developer/lista-lending/irm.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
