Suppliers can redeem previously supplied liquidity through the Withdraw tab on the lending page (/lending). The flow is:
- Select a supply position
- Enter a withdrawal amount (up to the withdrawable balance)
- Review the health factor impact live
- Confirm via the shared
ConfirmModal - A signed transaction is submitted to
/api/tx/submitand tracked via/api/tx/status/{hash}
Only the free (non-collateralised) portion of supplied funds is available for withdrawal.
withdrawableBalance = suppliedAmount − lockedCollateral
| Field | Description |
|---|---|
suppliedAmount |
Total funds currently supplied in this position |
lockedCollateral |
Funds reserved as collateral backing open borrow positions |
withdrawableBalance |
Funds available for immediate redemption |
| Field | Value |
|---|---|
| Asset | XLM |
| Total supplied | 5,000 XLM |
| Locked as collateral | 2,250 XLM |
| Available to withdraw | 2,750 XLM |
Attempting to withdraw 3,000 XLM is blocked with:
"Amount exceeds withdrawable balance of 2,750.00 XLM"
When a position carries outstanding debt, withdrawing reduces the collateral buffer and can lower the health factor. The post-withdrawal estimate uses a proportional model:
healthFactorAfter = currentHealthFactor × (suppliedAmount − withdrawal) / suppliedAmount
| Health Factor Range | Status | UI Behaviour |
|---|---|---|
| ≥ 2.0 | Healthy | Withdrawal allowed, no warning |
| 1.0 – < 2.0 | At Risk | Amber inline warning shown; submission still allowed |
| < 1.0 | Critical | Red inline warning shown; form validation blocks submission |
Positions with no outstanding debt are exempt from the health factor guard. The health factor row is hidden from the preview for those positions.
Starting position: healthFactor = 1.85, suppliedAmount = 5,000 XLM, outstandingDebt = 1,500 XLM.
| Withdrawal | healthFactorAfter |
Status |
|---|---|---|
| 500 XLM | 1.85 × 4,500 / 5,000 = 1.67 | At Risk (warning) |
| 1,000 XLM | 1.85 × 4,000 / 5,000 = 1.48 | At Risk (warning) |
| 2,750 XLM | 1.85 × 2,250 / 5,000 = 0.83 (blocked) | Critical |
The withdraw form reads its live supply positions from the existing /api/positions endpoint via the shared usePositions hook. The hook parses the route payload into the SupplyPosition shape expected by WithdrawForm, including:
suppliedAmountlockedCollateraloutstandingDebthealthFactor
If the request is still loading, the form shows a loading state. If the account has no withdrawable supply positions, it shows an empty state. If the request fails, it surfaces an error state (complete with a Retry button that allows users to manually re-trigger the fetch by invoking the hook's refetch callback) instead of falling back to static fixtures.
WithdrawForm
└─ onSubmit(LendingData) ──────────────────────────────────────────────┐
│
app/lending/page.tsx │
├─ handleWithdrawSubmit ◄─────────────────────────────────────────────┘
│ setWithdrawData(data)
│ setShowConfirmModal(true)
│
├─ ConfirmModal (type="withdraw")
│ onConfirm → handleConfirm()
│ POST /api/tx/submit
│ → setTxHash(hash)
│
└─ useTxStatus(txHash) → polls /api/tx/status/{hash} → Toast
| Field | Withdraw meaning |
|---|---|
asset |
Asset being withdrawn |
amount |
Withdrawal amount |
interestRate |
Always 0 (no interest on withdrawals) |
positionId |
Supply position ID |
outstandingDebt |
Outstanding debt on this position |
remainingDebt |
Remaining supplied after withdrawal |
collateralAmount |
Amount locked as collateral (informational) |
healthFactorBefore |
Health factor before withdrawal |
healthFactorAfter |
Projected health factor after withdrawal |
After a withdrawal is confirmed via ConfirmModal, TransactionSummary renders a confirmation breakdown in the right-hand panel with type="withdraw":
| Row | Source | Condition |
|---|---|---|
| Amount Redeemed | LendingData.amount |
Always shown |
| Remaining Supply | LendingData.remainingDebt |
Always shown |
| New Health Factor | LendingData.healthFactorAfter |
Only when outstandingDebt > 0 |
The empty state is shown until WithdrawForm calls its onSubmit callback with an amount greater than zero. No CalculationResult is used for the withdraw breakdown.
| File | Role |
|---|---|
components/features/lending/components/WithdrawForm.tsx |
Form component with validation and live preview |
components/features/lending/components/WithdrawForm.test.tsx |
RTL test suite |
components/features/lending/components/TabSelector.tsx |
Exposes the "Withdraw" tab |
components/features/lending/components/ConfirmModal.tsx |
Handles type="withdraw" confirmation |
app/lending/page.tsx |
Routes the withdraw tab and wires submission |
lib/lending/types.ts |
LendingActionType includes "withdraw" |