Source Code

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-found (err u101))
(define-constant err-unauthorized (err u102))
(define-constant err-invalid-amount (err u104))
(define-constant err-loan-active (err u109))
(define-constant err-insufficient-collateral (err u110))
(define-constant err-already-funded (err u111))

(define-data-var loan-nonce uint u0)

(define-map loans
  uint
  {
    borrower: principal,
    lender: principal,
    loan-amount: uint,
    interest-rate: uint,
    collateral-value: uint,
    ltv-ratio: uint,
    outstanding: uint,
    funded: bool,
    active: bool,
    start-block: uint,
    term-blocks: uint
  }
)

(define-map loan-payments
  {loan-id: uint, payment-id: uint}
  {amount: uint, block: uint, type: (string-ascii 20)}
)

(define-map payment-counter uint uint)
(define-map borrower-loans principal (list 50 uint))
(define-map lender-loans principal (list 50 uint))

(define-public (create-loan (lender principal) (amount uint) (rate uint) (collateral uint) (ltv uint) (term uint))
  (let
    (
      (loan-id (+ (var-get loan-nonce) u1))
      (borrower tx-sender)
    )
    (asserts! (> amount u0) err-invalid-amount)
    (asserts! (>= collateral (/ (* amount u100) ltv)) err-insufficient-collateral)
    (map-set loans loan-id {
      borrower: borrower,
      lender: lender,
      loan-amount: amount,
      interest-rate: rate,
      collateral-value: collateral,
      ltv-ratio: ltv,
      outstanding: amount,
      funded: false,
      active: false,
      start-block: u0,
      term-blocks: term
    })
    (map-set payment-counter loan-id u0)
    (map-set borrower-loans borrower (unwrap-panic (as-max-len? (append (default-to (list) (map-get? borrower-loans borrower)) loan-id) u50)))
    (map-set lender-loans lender (unwrap-panic (as-max-len? (append (default-to (list) (map-get? lender-loans lender)) loan-id) u50)))
    (var-set loan-nonce loan-id)
    (ok loan-id)
  )
)

(define-public (fund-loan (loan-id uint))
  (let
    (
      (loan (unwrap! (map-get? loans loan-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get lender loan)) err-unauthorized)
    (asserts! (not (get funded loan)) err-already-funded)
    (try! (stx-transfer? (get loan-amount loan) tx-sender (get borrower loan)))
    (map-set loans loan-id (merge loan {funded: true, active: true, start-block: stacks-block-height}))
    (ok true)
  )
)

(define-public (make-repayment (loan-id uint) (amount uint))
  (let
    (
      (loan (unwrap! (map-get? loans loan-id) err-not-found))
      (new-outstanding (if (>= amount (get outstanding loan)) u0 (- (get outstanding loan) amount)))
      (pid (+ (default-to u0 (map-get? payment-counter loan-id)) u1))
    )
    (asserts! (is-eq tx-sender (get borrower loan)) err-unauthorized)
    (asserts! (get active loan) err-loan-active)
    (try! (stx-transfer? amount tx-sender (get lender loan)))
    (map-set loan-payments {loan-id: loan-id, payment-id: pid} {amount: amount, block: stacks-block-height, type: "repayment"})
    (map-set payment-counter loan-id pid)
    (map-set loans loan-id (merge loan {outstanding: new-outstanding, active: (> new-outstanding u0)}))
    (ok true)
  )
)

(define-public (close-loan (loan-id uint))
  (let
    (
      (loan (unwrap! (map-get? loans loan-id) err-not-found))
      (total (calculate-payoff loan-id))
    )
    (asserts! (is-eq tx-sender (get borrower loan)) err-unauthorized)
    (asserts! (get active loan) err-loan-active)
    (try! (stx-transfer? total tx-sender (get lender loan)))
    (map-set loans loan-id (merge loan {outstanding: u0, active: false}))
    (ok true)
  )
)

(define-read-only (get-loan (loan-id uint))
  (ok (map-get? loans loan-id))
)

(define-read-only (get-borrower-loans (borrower principal))
  (ok (map-get? borrower-loans borrower))
)

(define-read-only (get-lender-loans (lender principal))
  (ok (map-get? lender-loans lender))
)

(define-read-only (calculate-payoff (loan-id uint))
  (let
    (
      (loan (unwrap-panic (map-get? loans loan-id)))
      (outstanding (get outstanding loan))
      (rate (get interest-rate loan))
      (elapsed (- stacks-block-height (get start-block loan)))
    )
    (+ outstanding (/ (* outstanding (* rate elapsed)) u10000000))
  )
)

(define-read-only (get-loan-health (loan-id uint))
  (let
    (
      (loan (unwrap-panic (map-get? loans loan-id)))
    )
    (ok {
      funded: (get funded loan),
      active: (get active loan),
      outstanding: (get outstanding loan),
      ltv: (/ (* (get outstanding loan) u100) (get collateral-value loan))
    })
  )
)

Functions (9)

FunctionAccessArgs
create-loanpubliclender: principal, amount: uint, rate: uint, collateral: uint, ltv: uint, term: uint
fund-loanpublicloan-id: uint
make-repaymentpublicloan-id: uint, amount: uint
close-loanpublicloan-id: uint
get-loanread-onlyloan-id: uint
get-borrower-loansread-onlyborrower: principal
get-lender-loansread-onlylender: principal
calculate-payoffread-onlyloan-id: uint
get-loan-healthread-onlyloan-id: uint