StellarKraal uses a health factor to determine whether a loan position is safe or eligible for liquidation. When collateral value drops relative to outstanding debt, the health factor falls below the safety threshold and any address can liquidate the position.
HF = (total_collateral_value Γ liquidation_threshold_bps) / (outstanding Γ 10_000) Γ 10_000
| Variable | Symbol | Description |
|---|---|---|
| Total collateral value | total_collateral_value |
Sum of appraised values of all collateral assets locked to the loan, in token base units |
| Liquidation threshold | liquidation_threshold_bps |
Protocol parameter controlling the safety margin, default 8000 (= 80%) |
| Outstanding debt | outstanding |
Current debt owed by the borrower (principal + accrued interest), in token base units |
| Health factor | HF |
Dimensionless safety ratio scaled by 10,000 |
Safe condition: HF >= 10_000 (ratio β₯ 1.0)
Liquidatable: HF < 10_000 (ratio < 1.0)
If
outstanding == 0the health factor isi128::MAX(fully repaid, cannot be liquidated).
let numerator = total_collateral_value * liq_thr as i128; // liq_thr = 8000
let denominator = outstanding * 10_000;
HF = (numerator / denominator) * 10_000| Parameter | Symbol | Default | Rationale |
|---|---|---|---|
| Loan-to-Value | LTV |
6000 bps (60%) | Caps initial disbursement at 60% of collateral; provides a 20 pp buffer before the liquidation threshold is breached |
| Liquidation Threshold | LIQ_THR |
8000 bps (80%) | HF drops below 1.0 when outstanding debt exceeds 80% of collateral value; the 20 pp gap between LTV and this threshold acts as a safety cushion to absorb price volatility before liquidation is triggered |
| Origination Fee | ORIG_FEE |
50 bps (0.5%) | Deducted from disbursement at loan creation |
| Interest Fee | INT_FEE |
1000 bps (10%) | Applied to the interest portion on repayment |
| Close Factor | CLOSE_FACTOR |
5000 bps (50%) | Maximum percentage of outstanding debt a liquidator can repay in one call; limits price impact and gives the borrower a chance to self-cure |
Setup
| Item | Value |
|---|---|
| Collateral (5 cattle @ 200 XLM each) | 1,000 XLM |
| Loan principal | 600 XLM (60% LTV) |
| Outstanding debt | 600 XLM |
Health factor
HF = (1000 Γ 8000) / (600 Γ 10_000) Γ 10_000
= 8_000_000 / 6_000_000 Γ 10_000
= 13_333 β
SAFE
Calling liquidate returns Error::HealthFactorSafe (code 7) β the contract rejects the call immediately.
Setup (same loan as above; cattle value falls to 700 XLM)
Health factor after price drop
HF = (700 Γ 8000) / (600 Γ 10_000) Γ 10_000
= 5_600_000 / 6_000_000 Γ 10_000
= 9_333 β οΈ LIQUIDATABLE
Close-factor cap
max_repay = 600 Γ 5000 / 10_000 = 300 XLM
Liquidator calls liquidate(liquidator, loan_id, 300).
State after partial liquidation
| Item | Before | After |
|---|---|---|
| Outstanding debt | 600 XLM | 300 XLM |
| Loan status | Active | Active |
HF = (700 Γ 8000) / (300 Γ 10_000) Γ 10_000
= 5_600_000 / 3_000_000 Γ 10_000
= 18_666 β
SAFE again
Setup (small loan, large collateral drop)
| Item | Value |
|---|---|
| Collateral value (post-drop) | 100 XLM |
| Outstanding debt | 200 XLM |
HF = (100 Γ 8000) / (200 Γ 10_000) Γ 10_000
= 800_000 / 2_000_000 Γ 10_000
= 4_000 β οΈ LIQUIDATABLE
Close-factor cap: 200 Γ 5000 / 10_000 = 100 XLM.
Liquidator calls liquidate(liquidator, loan_id, 100).
Outstanding becomes 200 β 100 = 100 XLM β loan stays Active.
A second liquidation call with repay_amount = 100:
Outstanding becomes 100 β 100 = 0 β loan status transitions to Liquidated.
The following describes exactly what happens inside the liquidate contract function.
- Contract is initialized β otherwise
Error::NotInitialized. - Contract is not paused β otherwise
Error::ContractPaused. repay_amount > 0β otherwiseError::InvalidAmount.liquidatorsigns the transaction (require_auth).- Loan identified by
loan_idexists β otherwiseError::LoanNotFound. - Loan status is
Activeβ otherwiseError::LoanAlreadyClosed.
1. Read LIQ_THR and CLOSE_FACTOR from instance storage.
2. Compute HF using:
HF = (total_collateral_value Γ LIQ_THR) / (outstanding Γ 10_000) Γ 10_000
3. If HF >= 10_000 β revert Error::HealthFactorSafe
4. Compute max_repay = outstanding Γ CLOSE_FACTOR / 10_000
If repay_amount > max_repay β revert Error::ExceedsCloseFactor
5. Transfer repay_amount tokens from liquidator β contract (SAC transfer)
6. outstanding -= repay_amount
7. If outstanding == 0:
loan.status = Liquidated
Else:
loan.status remains Active
8. Persist updated LoanRecord to storage.
9. Emit event:
topic: ("loan", "liquidated")
data: (loan_id, liquidator, repay_amount, outstanding, status)
The contract does not transfer collateral on-chain. Collateral release to the liquidator is handled off-chain via the oracle/settlement layer after the loan/liquidated event is observed. The backend event listener (src/contractEventListener.ts) processes this event and updates the local database.
stellar contract invoke \
--id "$CONTRACT_ID" \
--fn liquidate \
--arg address:$LIQUIDATOR_ADDRESS \
--arg u64:$LOAN_ID \
--arg i128:$REPAY_AMOUNT \
--network testnet \
--rpc-url https://soroban-testnet.stellar.org \
--source "$LIQUIDATOR_ADDRESS"Query the health factor before liquidating:
stellar contract invoke \
--id "$CONTRACT_ID" \
--fn health_factor \
--arg u64:$LOAN_ID \
--network testnet \
--rpc-url https://soroban-testnet.stellar.orgStellarKraal supports partial liquidations governed by the close factor:
- A liquidator calls
liquidate(liquidator, loan_id, repay_amount). - The contract checks
HF < 10_000; reverts withHealthFactorSafeotherwise. repay_amountmust satisfyrepay_amount <= outstanding Γ close_factor / 10_000.- The liquidator transfers
repay_amounttokens to the contract. outstandingis reduced byrepay_amount.- If
outstandingreaches 0, loan status becomesLiquidated; otherwise it staysActive. - The liquidator receives collateral value proportional to the repaid debt (handled off-chain via oracle settlement in the current implementation).
No liquidation bonus is applied in the current contract version. The incentive for liquidators is the discounted collateral acquisition negotiated off-chain.
| Code | Name | Meaning |
|---|---|---|
| 7 | HealthFactorSafe |
Loan HF >= 10_000; liquidation rejected |
| 11 | ExceedsCloseFactor |
repay_amount exceeds close factor cap |
| 5 | LoanNotFound |
Invalid loan_id |
| 9 | LoanAlreadyClosed |
Loan is Repaid or already Liquidated |
| 13 | ContractPaused |
Contract paused; liquidations blocked |
- Smart Contract Interface β full public API, all error codes,
liquidateandhealth_factorfunction signatures, and additionalstellar-cliexamples - Smart contract source:
contracts/stellarkraal/src/lib.rs - Frontend health gauge:
frontend/src/components/HealthGauge.tsx - Backend health endpoint:
GET /api/health/:loanId - Backend event listener:
backend/src/contractEventListener.ts