Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
- **High Efficiency (HE) mode** — pools can define HE asset groups via `poolAssetsHEConfig`, each with a `heCategory` number. `activeHeCategory` activates when all borrows belong to a single HE category **and** the total borrow exceeds the standard collateral limit. The HE borrow limit is calculated from supply assets within the same HE category using `heCollateralFactor` and `heLiquidationThreshold` instead of standard values, allowing higher LTV within the category.
- `activeHeCategory` field in `UserData` — `-1` means HE is inactive, positive integer is the active category
- `predictedHeCategory` field in `UserData` — HE category that would activate on next borrow (based on current supply), used to show available HE borrow limit in UI
- `borrowLimitsWithEmode` field in `UserData` — per-asset max borrow amounts under HE limits
- `availableToBorrowWithEmode` field in `UserData` — total available to borrow under HE limits
- `borrowLimitsWithHeMode` field in `UserData` — per-asset max borrow amounts under HE limits
- `availableToBorrowWithHeMode` field in `UserData` — total available to borrow under HE limits
- New functions:
- `determineHeCategory()` — derives active HE category from current borrows
- `exceedsStandardBorrowLimit()` — checks whether total borrow exceeds standard collateral limit
- `getAvailableToBorrowWithEMode()` — available-to-borrow under HE limits; shows HE limit whenever all borrows are within a single HE category, regardless of whether standard limit is already exceeded
- `calculateRepayToExitEMode()` — how much to repay to drop back to standard mode
- `getAvailableToBorrowWithHeMode()` — available-to-borrow under HE limits; shows HE limit whenever all borrows are within a single HE category, regardless of whether standard limit is already exceeded
- `calculateRepayToExitHeMode()` — how much to repay to drop back to standard mode
- New types: `PoolAssetHEConfig` (`title`, `assets`, `heCategory`)
### Changed
- Breaking rename of the public High Efficiency API from legacy `EMode` names to canonical `HeMode` names in exports and `UserData` fields
### Fixed
- `predictHealthFactor` now applies HE thresholds only when borrow exceeds the standard collateral limit
- `ClassicCollector` now fetches prices from all sources in parallel via `Promise.any` instead of sequentially
Expand Down
252 changes: 252 additions & 0 deletions docs/2. Core Concepts/2.7. High Efficiency Mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
# High Efficiency (HE) mode

## Table of Contents
1. [Overview](#overview)
2. [Pool-Level Configuration](#pool-level-configuration)
3. [Activation Rules](#activation-rules)
4. [What Changes When HE Becomes Active](#what-changes-when-he-becomes-active)
5. [Concrete Scenarios](#concrete-scenarios)
6. [UserData Fields](#userdata-fields)
7. [Common Misunderstandings](#common-misunderstandings)
8. [Integration Guidance](#integration-guidance)

## Overview

In this repository, the canonical user-facing name is `High Efficiency (HE) mode`.

The purpose of HE mode is to allow higher capital efficiency for groups of closely related assets inside the same pool. The implementation is category-based:

- The pool defines HE groups through `poolAssetsHEConfig`
- Each asset carries `heCategory`, `heCollateralFactor`, and `heLiquidationThreshold`
- Borrow-side category detection and supply-side benefit are related, but not identical

This last point is the main source of confusion:

- The HE category is determined from the borrowed assets
- The HE benefit is applied only to supplied assets that belong to the same category

So HE is not simply "enabled because the user supplied an HE asset". It depends on both sides of the position.

## Pool-Level Configuration

At the pool level, categories are defined by `poolAssetsHEConfig`.

Example from `MAINNET_CLASSIC_HE_POOL_CONFIG`:

| HE category | Assets |
| --- | --- |
| `1` | `TON`, `tsTON` |
| `2` | `TUSDT`, `TUSDe` |

This category list is the basis for all HE calculations in the SDK.

**Section sources**
- [mainnet.ts](file://src/constants/pools/mainnet.ts#L255-L259)
- [Master.ts](file://src/types/Master.ts#L26-L38)

## Activation Rules

The SDK treats "HE-aware borrowing capacity" and "active HE mode" as two different states.

### HE-aware borrowing capacity

`getAvailableToBorrowWithHeMode()` can already show a larger borrowing limit before HE is fully active:

- If the user has no debt, the function finds the best HE category supported by current supplied assets
- If the user already has debt and all borrowed assets belong to one HE category, the function uses that category for HE-aware borrow-limit calculation
- If debt is mixed across categories, it falls back to standard mode

### Active HE mode

`activeHeCategory` becomes positive only when both conditions are true:

1. All borrowed assets belong to the same positive HE category
2. Total debt exceeds the standard borrow limit

Until both are true, the account is still treated as standard for health and liquidation calculations.

**Section sources**
- [math.ts](file://src/api/math.ts#L185-L245)
- [math.ts](file://src/api/math.ts#L367-L483)
- [math.ts](file://src/api/math.ts#L694-L738)
- [parser.ts](file://src/api/parser.ts#L394-L405)

## What Changes When HE Becomes Active

When HE becomes active for category `N`:

- Borrowing power uses `heCollateralFactor` for supplied assets in category `N`
- Health and liquidation calculations use `heLiquidationThreshold` for supplied assets in category `N`
- Supplied assets outside category `N` still use their standard coefficients

This means HE is selective. Even with an active category, only matching supplied collateral receives the HE benefit.

**Section sources**
- [math.ts](file://src/api/math.ts#L384-L432)
- [math.ts](file://src/api/math.ts#L722-L732)

## Concrete Scenarios

Assume the pool categories are:

- Category `1`: `TON`, `tsTON`
- Category `2`: `TUSDT`, `TUSDe`

The scenarios below describe the SDK behavior without inventing specific LTV numbers.

### Scenario 1: Supply `TON`, no debt yet

Position:

- Supply: `TON`
- Borrow: none

Result:

- `predictedHeCategory = 1`
- `activeHeCategory = -1`
- `availableToBorrowWithHeMode` can be larger than `availableToBorrow`

Why:

- The user has no debt, so the SDK looks at supplied assets and finds the best HE category available for a future borrow
- HE is not active yet because there is no debt position

### Scenario 2: Supply `TON`, borrow a small amount of `tsTON`

Position:

- Supply: `TON`
- Borrow: `tsTON`
- Total debt is still within the standard borrow limit

Result:

- All debt belongs to category `1`
- `predictedHeCategory = 1`
- `activeHeCategory = -1`
- `availableToBorrowWithHeMode` is calculated for category `1`
- Health and liquidation still use standard thresholds

Why:

- `determineHeCategory()` looks only at debt, so it sees a single HE category
- But the account has not crossed the standard borrow limit yet
- Therefore the SDK exposes HE-aware borrowing headroom, while the position is still standard for risk calculations

This is the most non-obvious state in the current implementation.

### Scenario 3: Supply `TON`, keep borrowing `tsTON` until the standard limit is exceeded

Position:

- Supply: `TON`
- Borrow: `tsTON`
- Total debt is now above the standard borrow limit

Result:

- `predictedHeCategory = 1`
- `activeHeCategory = 1`
- Borrowing power for supplied `TON` can use `heCollateralFactor`
- Health and liquidation for supplied `TON` can use `heLiquidationThreshold`

Why:

- Debt is still entirely inside category `1`
- The account has now crossed the standard borrow limit
- This is the point where HE becomes truly active

### Scenario 4: Supply `TON`, borrow `TUSDT`

Position:

- Supply: `TON` (category `1`)
- Borrow: `TUSDT` (category `2`)

Result:

- Debt-side category is `2`
- If this is the only debt, `determineHeCategory()` sees a single category and can return `2`
- But supplied `TON` is not in category `2`
- As a result, category `2` does not get extra HE benefit from the supplied `TON`

Practical effect:

- If the account is still within the standard limit, `activeHeCategory = -1`
- If the account exceeds the standard limit, `activeHeCategory` can become `2`
- Even then, the supplied `TON` still uses standard coefficients, because HE benefits apply only to supplied assets in the active category

This is another easy place to misunderstand the feature. A single-category debt is not enough to guarantee a useful HE boost.

### Scenario 5: Supply `TON`, borrow both `tsTON` and `TUSDT`

Position:

- Supply: `TON`
- Borrow: `tsTON` and `TUSDT`

Result:

- Debt spans categories `1` and `2`
- `determineHeCategory()` returns `-1`
- `activeHeCategory = -1`
- `availableToBorrowWithHeMode` falls back to the standard calculation

Why:

- Mixed borrow categories disable HE immediately

**Section sources**
- [math.ts](file://src/api/math.ts#L209-L245)
- [math.ts](file://src/api/math.ts#L367-L483)
- [math.ts](file://src/api/math.ts#L694-L738)
- [tests/supply_withdraw_classic_he.ts](file://tests/supply_withdraw_classic_he.ts#L27-L29)

## UserData Fields

The HE-related fields in `UserDataActive` have different purposes:

| Field | Meaning |
| --- | --- |
| `availableToBorrowWithHeMode` | Borrowing capacity from the HE-aware calculation |
| `borrowLimitsWithHeMode` | Per-asset borrow limits derived from `availableToBorrowWithHeMode` |
| `predictedHeCategory` | The category used by the HE-aware borrow calculation |
| `activeHeCategory` | The category that is actually active for risk calculations, or `-1` if HE is inactive |

The important distinction:

- `predictedHeCategory` answers: "Which category is the HE-aware borrow calculation using?"
- `activeHeCategory` answers: "Is the account currently in active HE mode for health/liquidation?"

When there is no debt, `predictedHeCategory` behaves like a true forward-looking category for the next borrow. Once debt exists, it can instead reflect the current single-category debt being used by `getAvailableToBorrowWithHeMode()`.

**Section sources**
- [User.ts](file://src/types/User.ts#L62-L78)
- [parser.ts](file://src/api/parser.ts#L394-L500)

## Common Misunderstandings

- HE is not activated by supplied collateral alone. Debt composition matters.
- The HE category is determined from the borrowed assets, not from the supplied assets.
- The HE benefit applies only to supplied assets in the same category as the debt-side HE category.
- `predictedHeCategory` does not mean HE is currently active.
- `activeHeCategory = -1` can coexist with a larger `availableToBorrowWithHeMode`.
- Leaving active HE mode does not require changing debt category. Repaying until the position fits inside the standard borrow limit is enough.

For the last point, use `calculateRepayToExitHeMode()`.

**Section sources**
- [math.ts](file://src/api/math.ts#L248-L365)
- [math.ts](file://src/api/math.ts#L367-L483)

## Integration Guidance

For front-end and integration logic:

- Use `activeHeCategory` as the authoritative signal that health and liquidation are currently using HE thresholds
- Use `availableToBorrowWithHeMode` and `borrowLimitsWithHeMode` as UI guidance for HE-aware borrowing headroom
- Explain `predictedHeCategory` to users as a potential or calculation category, not as proof that the account is already in active HE mode
- If you show both standard and HE-aware limits, label them clearly to avoid implying that HE is already active

This wording matches the current SDK logic and avoids the most common UI misunderstanding: showing HE borrow headroom as if it were already the live liquidation regime.
1 change: 1 addition & 0 deletions docs/2. Core Concepts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ This section contains documentation for Core Concepts.
- [2.4. Subaccounts](./2.4. Subaccounts.md)
- [2.5. Price Validation And Medianization](./2.5. Price Validation And Medianization.md)
- [2.6. Jetton Vs Ton Asset Handling](./2.6. Jetton Vs Ton Asset Handling.md)
- [2.7. High Efficiency Mode](./2.7. High Efficiency Mode.md)
67 changes: 60 additions & 7 deletions docs/4. Api Reference/4.3. User Interaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
2. [Initialization and Provider Setup](#initialization-and-provider-setup)
3. [User State Synchronization](#user-state-synchronization)
4. [Data Parsing and Type Mapping](#data-parsing-and-type-mapping)
5. [Integration with Oracle Parsers](#integration-with-oracle-parsers)
6. [Usage Patterns and Health Monitoring](#usage-patterns-and-health-monitoring)
7. [Principal Balances and Operation Eligibility](#principal-balances-and-operation-eligibility)
8. [Error Scenarios](#error-scenarios)
9. [Master Contract Collaboration](#master-contract-collaboration)
10. [State Transition Diagram](#state-transition-diagram)
5. [High Efficiency (HE) Mode Fields](#high-efficiency-he-mode-fields)
6. [Integration with Oracle Parsers](#integration-with-oracle-parsers)
7. [Usage Patterns and Health Monitoring](#usage-patterns-and-health-monitoring)
8. [Principal Balances and Operation Eligibility](#principal-balances-and-operation-eligibility)
9. [Error Scenarios](#error-scenarios)
10. [Master Contract Collaboration](#master-contract-collaboration)
11. [State Transition Diagram](#state-transition-diagram)

## Initialization and Provider Setup

Expand Down Expand Up @@ -96,14 +97,18 @@ class UserData {
+fullyParsed : boolean
+withdrawalLimits : Dictionary~bigint, bigint~
+borrowLimits : Dictionary~bigint, bigint~
+borrowLimitsWithHeMode : Dictionary~bigint, bigint~
+supplyBalance : bigint
+borrowBalance : bigint
+availableToBorrow : bigint
+availableToBorrowWithHeMode : bigint
+limitUsedPercent : number
+limitUsed : bigint
+healthFactor : number
+liquidationData : LiquidationData
+havePrincipalWithoutPrice : boolean
+predictedHeCategory : number
+activeHeCategory : number
}
class UserLiteData {
<<interface>>
Expand All @@ -128,14 +133,18 @@ class UserDataActive {
<<interface>>
+withdrawalLimits : Dictionary~bigint, bigint~
+borrowLimits : Dictionary~bigint, bigint~
+borrowLimitsWithHeMode : Dictionary~bigint, bigint~
+supplyBalance : bigint
+borrowBalance : bigint
+availableToBorrow : bigint
+availableToBorrowWithHeMode : bigint
+limitUsedPercent : number
+limitUsed : bigint
+healthFactor : number
+liquidationData : LiquidationData
+havePrincipalWithoutPrice : boolean
+predictedHeCategory : number
+activeHeCategory : number
}
class UserDataInactive {
<<interface>>
Expand Down Expand Up @@ -173,11 +182,55 @@ The `parseUserData` function computes:
- **Health Factor**: Ratio of collateral value to debt limit
- **Liquidation Status**: Whether user is eligible for liquidation
- **Operation Limits**: Maximum withdraw/borrow amounts
- **HE-aware Borrow Metrics**: Borrow capacity and per-asset limits under HE-aware calculation

**Section sources**
- [parser.ts](file://src/api/parser.ts#L358-L457)
- [User.ts](file://src/types/User.ts#L1-L119)

## High Efficiency (HE) Mode Fields

The canonical user-facing name is `High Efficiency (HE) mode`.

`parseUserData()` exposes both standard borrowing fields and HE-aware borrowing fields:

- `availableToBorrow`
- `borrowLimits`
- `availableToBorrowWithHeMode`
- `borrowLimitsWithHeMode`
- `predictedHeCategory`
- `activeHeCategory`

These fields are easy to confuse, because they represent two different layers of logic:

- The HE-aware borrow calculation answers: "How much could this account borrow if HE rules for one category are applied?"
- The active HE state answers: "Is the account already using HE thresholds for health and liquidation?"

Field-by-field meaning:

| Field | Meaning |
| --- | --- |
| `availableToBorrow` | Standard borrow headroom using standard collateral factors |
| `borrowLimits` | Per-asset standard borrow limits |
| `availableToBorrowWithHeMode` | HE-aware borrow headroom from `getAvailableToBorrowWithHeMode()` |
| `borrowLimitsWithHeMode` | Per-asset limits derived from the HE-aware headroom |
| `predictedHeCategory` | The category returned by the HE-aware borrow calculation |
| `activeHeCategory` | The category that is actually active for risk calculations, or `-1` if inactive |

Important behavioral detail:

- `predictedHeCategory` can be positive while `activeHeCategory` is still `-1`
- This happens when the account has a valid single-category HE borrowing path, but total debt has not yet exceeded the standard borrow limit
- In that state, the UI can show HE-aware borrow headroom, but health and liquidation still follow standard thresholds

For a full walkthrough with concrete position examples, see [High Efficiency (HE) mode](../2. Core Concepts/2.7. High Efficiency Mode.md).

**Section sources**
- [parser.ts](file://src/api/parser.ts#L394-L500)
- [math.ts](file://src/api/math.ts#L367-L483)
- [math.ts](file://src/api/math.ts#L694-L738)
- [User.ts](file://src/types/User.ts#L62-L78)

## Integration with Oracle Parsers

While `UserContract` itself does not directly use oracle parsers, it relies on price data that is typically obtained through `OracleParser` implementations.
Expand Down Expand Up @@ -424,4 +477,4 @@ The user contract transitions between `Active` and `Inactive` states based on bl
- [AbstractMaster.ts](file://src/contracts/AbstractMaster.ts#L1-L422)
- [AbstractOracleParser.ts](file://src/api/parsers/AbstractOracleParser.ts#L1-L16)
- [ClassicOracleParser.ts](file://src/api/parsers/ClassicOracleParser.ts#L1-L20)
- [PythOracleParser.ts](file://src/api/parsers/PythOracleParser.ts#L1-L34)
- [PythOracleParser.ts](file://src/api/parsers/PythOracleParser.ts#L1-L34)
Loading