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
64 changes: 43 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,45 +181,65 @@ pub fn unlock_nft(env: Env, nft_id: u64)
**Key Functions**:
```rust
// Initialize the contract
pub fn initialize(env: Env, admin: Address, pool_address: Address)
pub fn initialize(
env: Env,
nft_contract: Address,
lending_pool: Address,
token: Address,
admin: Address
)

// Request a loan
pub fn request_loan(
env: Env,
borrower: Address,
nft_id: u64,
amount: i128
) -> u64
pub fn request_loan(env: Env, borrower: Address, amount: i128, term: u32) -> u32

// Approve a loan
pub fn approve_loan(env: Env, loan_id: u64)
// Approve a loan (admin only)
pub fn approve_loan(env: Env, loan_id: u32)

// Repay loan
pub fn repay_loan(env: Env, loan_id: u64, amount: i128)
pub fn repay(env: Env, borrower: Address, loan_id: u32, amount: i128)

// Cancel pending loan (borrower only)
pub fn cancel_loan(env: Env, borrower: Address, loan_id: u32)

// Reject pending loan (admin only)
pub fn reject_loan(env: Env, loan_id: u32, reason: String)

// Get loan details
pub fn get_loan(env: Env, loan_id: u64) -> Loan
// Get full loan details (triggers accrual)
pub fn get_loan(env: Env, loan_id: u32) -> Result<Loan, LoanError>

// Check loan status
pub fn get_loan_status(env: Env, loan_id: u64) -> LoanStatus
// Get loan status only (no accrual) - lightweight view for indexers
pub fn get_loan_status(env: Env, loan_id: u32) -> Result<LoanStatus, LoanError>

// Get all loan IDs for a borrower
pub fn get_borrower_loans(env: Env, borrower: Address) -> Vec<u32>

// Get loan IDs for a borrower filtered by status (no accrual)
pub fn get_borrower_loans_by_status(env: Env, borrower: Address, status: LoanStatus) -> Vec<u32>
```

**Loan States**:
```rust
pub enum LoanStatus {
Requested, // Loan requested, awaiting approval
Pending, // Loan requested, awaiting approval
Approved, // Approved, funds disbursed
Active, // Repayment in progress
Repaid, // Fully repaid
Defaulted, // Payment missed
Defaulted, // Payment missed, collateral seized
Cancelled, // Cancelled by borrower before approval
Rejected, // Rejected by admin
Liquidated, // Liquidated by liquidator
}
```

**View Functions**:
- `get_loan_status(loan_id)` - Returns only the status enum without running accrual calculations. Use this for lightweight queries when you only need to know loan state.
- `get_borrower_loans_by_status(borrower, status)` - Returns loan IDs filtered by status. Useful for indexers to query specific loan states without fetching full loan data.

**Business Logic**:
- Minimum credit score: 600
- Maximum loan-to-value: 80%
- Interest rate: Based on credit score
- Repayment period: Configurable
- Minimum credit score: 500 (configurable via `set_min_score`)
- Maximum loans per borrower: 3 (configurable via `set_max_loans_per_borrower`)
- Interest rate: Oracle-based or configurable default (1200 BPS)
- Late fee rate: Configurable (500 BPS default)
- Maximum extensions: 3 (configurable by calling `extend_loan`)

**Tests**:
- ✅ Loan request flow
Expand All @@ -228,6 +248,8 @@ pub enum LoanStatus {
- ✅ Low score rejection
- ✅ Unauthorized repayment prevention
- ✅ Access controls
- ✅ get_loan_status returns correct status without accrual
- ✅ get_borrower_loans_by_status filters correctly

### 3. Lending Pool Contract

Expand Down
35 changes: 29 additions & 6 deletions loan_manager/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1160,18 +1160,19 @@ impl LoanManager {
Ok(loan)
}

/// Returns the exact current total debt (principal + accrued interest + accrued late fee) for a loan.
/// This amount matches exactly what `repay` would charge at the current ledger.
pub fn quote_total_debt(env: Env, loan_id: u32) -> Result<i128, LoanError> {
/// Returns the loan status without triggering interest/late fee accrual.
/// This is a lightweight view function for indexers and frontend queries
/// that only need to know the current state of a loan.
pub fn get_loan_status(env: Env, loan_id: u32) -> Result<LoanStatus, LoanError> {
let loan_key = DataKey::Loan(loan_id);
let mut loan: Loan = env
// Read loan directly without mutating reference to avoid accrual
let loan: Loan = env
.storage()
.persistent()
.get(&loan_key)
.ok_or(LoanError::LoanNotFound)?;
Self::bump_persistent_ttl(&env, &loan_key);
let (total_debt, _) = Self::current_total_debt(&env, &mut loan)?;
Ok(total_debt)
Ok(loan.status)
}

pub fn repay(env: Env, borrower: Address, loan_id: u32, amount: i128) -> Result<(), LoanError> {
Expand Down Expand Up @@ -2067,6 +2068,28 @@ impl LoanManager {
.unwrap_or(Vec::new(&env))
}

/// Returns loan IDs for a borrower filtered by status, without triggering accrual.
/// This allows indexers to query only loans in a specific state.
pub fn get_borrower_loans_by_status(env: Env, borrower: Address, status: LoanStatus) -> Vec<u32> {
Self::bump_instance_ttl(&env);
let all_loans: Vec<u32> = env
.storage()
.instance()
.get(&DataKey::BorrowerLoans(borrower.clone()))
.unwrap_or(Vec::new(&env));
let mut matching_loans = Vec::new(&env);
for loan_id in all_loans.iter() {
let loan_key = DataKey::Loan(loan_id);
if let Some(loan) = env.storage().persistent().get::<DataKey, Loan>(&loan_key) {
Self::bump_persistent_ttl(&env, &loan_key);
if loan.status == status {
matching_loans.push_back(loan_id);
}
}
}
matching_loans
}

pub fn get_min_score(env: Env) -> u32 {
Self::bump_instance_ttl(&env);
env.storage()
Expand Down
Loading
Loading