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-already-exists (err u103))
(define-constant err-invalid-amount (err u104))
(define-constant err-loan-defaulted (err u105))
(define-constant err-insufficient-credit (err u106))

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

(define-map credit-scores
  principal
  {
    base-score: uint,
    onchain-activity-score: uint,
    repayment-history-score: uint,
    total-borrowed: uint,
    total-repaid: uint,
    defaults: uint,
    credit-limit: uint
  }
)

(define-map undercollateralized-loans
  uint
  {
    borrower: principal,
    lender: principal,
    principal-amount: uint,
    interest-rate: uint,
    collateral-amount: uint,
    collateral-ratio: uint,
    outstanding-balance: uint,
    loan-start: uint,
    loan-end: uint,
    repaid: bool,
    defaulted: bool
  }
)

(define-map loan-repayments
  {loan-id: uint, payment-id: uint}
  {
    amount: uint,
    payment-block: uint
  }
)

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

(define-public (initialize-credit-profile (base-score uint))
  (begin
    (asserts! (is-none (map-get? credit-scores tx-sender)) err-already-exists)
    (asserts! (<= base-score u1000) err-invalid-amount)
    (map-set credit-scores tx-sender
      {
        base-score: base-score,
        onchain-activity-score: u0,
        repayment-history-score: u0,
        total-borrowed: u0,
        total-repaid: u0,
        defaults: u0,
        credit-limit: (* base-score u1000)
      }
    )
    (ok true)
  )
)

(define-public (request-loan (lender principal) (amount uint) (collateral-amount uint) (interest-rate uint) (duration-blocks uint))
  (let
    (
      (credit (unwrap! (map-get? credit-scores tx-sender) err-not-found))
      (loan-id (+ (var-get loan-nonce) u1))
      (collateral-ratio (if (> amount u0) (/ (* collateral-amount u10000) amount) u0))
    )
    (asserts! (> amount u0) err-invalid-amount)
    (asserts! (<= amount (get credit-limit credit)) err-insufficient-credit)
    (asserts! (< collateral-ratio u10000) err-invalid-amount)
    (try! (stx-transfer? collateral-amount tx-sender (as-contract tx-sender)))
    (map-set undercollateralized-loans loan-id
      {
        borrower: tx-sender,
        lender: lender,
        principal-amount: amount,
        interest-rate: interest-rate,
        collateral-amount: collateral-amount,
        collateral-ratio: collateral-ratio,
        outstanding-balance: amount,
        loan-start: stacks-block-height,
        loan-end: (+ stacks-block-height duration-blocks),
        repaid: false,
        defaulted: false
      }
    )
    (map-set payment-count loan-id u0)
    (map-set borrower-loans tx-sender
      (unwrap-panic (as-max-len? (append (default-to (list) (map-get? borrower-loans tx-sender)) loan-id) u50)))
    (map-set credit-scores tx-sender (merge credit {
      total-borrowed: (+ (get total-borrowed credit) amount)
    }))
    (var-set loan-nonce loan-id)
    (ok loan-id)
  )
)

(define-public (fund-loan (loan-id uint))
  (let
    (
      (loan (unwrap! (map-get? undercollateralized-loans loan-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get lender loan)) err-unauthorized)
    (try! (stx-transfer? (get principal-amount loan) tx-sender (get borrower loan)))
    (ok true)
  )
)

(define-public (repay-loan (loan-id uint) (amount uint))
  (let
    (
      (loan (unwrap! (map-get? undercollateralized-loans loan-id) err-not-found))
      (credit (unwrap! (map-get? credit-scores tx-sender) err-not-found))
      (payment-id (+ (default-to u0 (map-get? payment-count loan-id)) u1))
      (new-balance (if (>= amount (get outstanding-balance loan)) u0 (- (get outstanding-balance loan) amount)))
    )
    (asserts! (is-eq tx-sender (get borrower loan)) err-unauthorized)
    (asserts! (not (get repaid loan)) err-already-exists)
    (asserts! (not (get defaulted loan)) err-loan-defaulted)
    (try! (stx-transfer? amount tx-sender (get lender loan)))
    (map-set loan-repayments {loan-id: loan-id, payment-id: payment-id}
      {
        amount: amount,
        payment-block: stacks-block-height
      }
    )
    (map-set payment-count loan-id payment-id)
    (map-set undercollateralized-loans loan-id (merge loan {
      outstanding-balance: new-balance,
      repaid: (is-eq new-balance u0)
    }))
    (if (is-eq new-balance u0)
      (begin
        (try! (as-contract (stx-transfer? (get collateral-amount loan) tx-sender (get borrower loan))))
        (map-set credit-scores tx-sender (merge credit {
          total-repaid: (+ (get total-repaid credit) (get principal-amount loan)),
          repayment-history-score: (+ (get repayment-history-score credit) u10),
          credit-limit: (+ (get credit-limit credit) u10000)
        }))
      )
      (map-set credit-scores tx-sender (merge credit {
        total-repaid: (+ (get total-repaid credit) amount)
      }))
    )
    (ok true)
  )
)

(define-public (mark-default (loan-id uint))
  (let
    (
      (loan (unwrap! (map-get? undercollateralized-loans loan-id) err-not-found))
      (credit (unwrap! (map-get? credit-scores (get borrower loan)) err-not-found))
    )
    (asserts! (is-eq tx-sender (get lender loan)) err-unauthorized)
    (asserts! (> stacks-block-height (get loan-end loan)) err-not-found)
    (asserts! (> (get outstanding-balance loan) u0) err-invalid-amount)
    (try! (as-contract (stx-transfer? (get collateral-amount loan) tx-sender (get lender loan))))
    (map-set undercollateralized-loans loan-id (merge loan {defaulted: true}))
    (map-set credit-scores (get borrower loan) (merge credit {
      defaults: (+ (get defaults credit) u1),
      credit-limit: (/ (get credit-limit credit) u2)
    }))
    (ok true)
  )
)

(define-read-only (get-credit-score (user principal))
  (ok (map-get? credit-scores user))
)

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

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

(define-read-only (get-repayment (loan-id uint) (payment-id uint))
  (ok (map-get? loan-repayments {loan-id: loan-id, payment-id: payment-id}))
)

Functions (9)

FunctionAccessArgs
initialize-credit-profilepublicbase-score: uint
request-loanpubliclender: principal, amount: uint, collateral-amount: uint, interest-rate: uint, duration-blocks: uint
fund-loanpublicloan-id: uint
repay-loanpublicloan-id: uint, amount: uint
mark-defaultpublicloan-id: uint
get-credit-scoreread-onlyuser: principal
get-loanread-onlyloan-id: uint
get-borrower-loansread-onlyborrower: principal
get-repaymentread-onlyloan-id: uint, payment-id: uint