Source Code

;; BitTrust Core Lending Contract
;; A reputation-based micro-lending platform on Stacks

;; Constants
(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-insufficient-collateral (err u103))
(define-constant err-loan-active (err u104))
(define-constant err-loan-not-active (err u105))
(define-constant err-invalid-amount (err u106))
(define-constant err-already-exists (err u107))
(define-constant err-insufficient-balance (err u108))

;; Data Variables
(define-data-var loan-nonce uint u0)

;; Data Maps
(define-map loans
  { loan-id: uint }
  {
    borrower: principal,
    lender: principal,
    amount: uint,
    collateral: uint,
    interest-rate: uint,
    duration: uint,
    start-block: uint,
    due-block: uint,
    repaid-amount: uint,
    status: (string-ascii 20),
    collateral-ratio: uint
  }
)

(define-map user-stats
  { user: principal }
  {
    total-borrowed: uint,
    total-lent: uint,
    active-loans-as-borrower: uint,
    active-loans-as-lender: uint,
    total-repaid: uint,
    defaults: uint
  }
)

(define-map collateral-vault
  { loan-id: uint }
  { amount: uint }
)

;; Read-only functions
(define-read-only (get-loan (loan-id uint))
  (map-get? loans { loan-id: loan-id })
)

(define-read-only (get-user-stats (user principal))
  (default-to
    { total-borrowed: u0, total-lent: u0, active-loans-as-borrower: u0, 
      active-loans-as-lender: u0, total-repaid: u0, defaults: u0 }
    (map-get? user-stats { user: user })
  )
)

(define-read-only (calculate-repayment-amount (loan-id uint))
  (let
    (
      (loan (unwrap! (get-loan loan-id) err-not-found))
      (principal-amount (get amount loan))
      (interest-rate (get interest-rate loan))
    )
    (ok (+ principal-amount (/ (* principal-amount interest-rate) u10000)))
  )
)

(define-read-only (is-loan-overdue (loan-id uint))
  (let
    (
      (loan (unwrap! (get-loan loan-id) err-not-found))
    )
    (ok (> stacks-block-height (get due-block loan)))
  )
)

;; Public functions

;; Create a direct peer-to-peer loan
(define-public (create-loan (borrower principal) (amount uint) (collateral-amount uint)
                            (interest-rate uint) (duration uint) (min-collateral-ratio uint))
  (let
    (
      (loan-id (var-get loan-nonce))
      (collateral-ratio (if (> collateral-amount u0)
                           (/ (* collateral-amount u100) amount)
                           u0))
    )
    (asserts! (> amount u0) err-invalid-amount)
    (asserts! (not (is-eq borrower tx-sender)) err-invalid-amount)
    (asserts! (<= interest-rate u10000) err-invalid-amount) ;; Max 100%
    (asserts! (> duration u0) err-invalid-amount)
    (asserts! (>= collateral-ratio min-collateral-ratio) err-insufficient-collateral)
    
    ;; Transfer loan from lender to borrower
    (try! (stx-transfer? amount tx-sender borrower))
    
    ;; Transfer collateral from borrower to lender (held as collateral)
    (if (> collateral-amount u0)
      (try! (stx-transfer? collateral-amount borrower tx-sender))
      true
    )
    
    ;; Store collateral info
    (map-set collateral-vault { loan-id: loan-id } { amount: collateral-amount })
    
    ;; Create loan record
    (map-set loans
      { loan-id: loan-id }
      {
        borrower: borrower,
        lender: tx-sender,
        amount: amount,
        collateral: collateral-amount,
        interest-rate: interest-rate,
        duration: duration,
        start-block: stacks-block-height,
        due-block: (+ stacks-block-height duration),
        repaid-amount: u0,
        status: "active",
        collateral-ratio: collateral-ratio
      }
    )
    
    ;; Update user stats
    (update-user-stats-borrow borrower amount)
    (update-user-stats-lend tx-sender amount)
    
    (var-set loan-nonce (+ loan-id u1))
    (ok loan-id)
  )
)

;; Repay loan
(define-public (repay-loan (loan-id uint) (amount uint))
  (let
    (
      (loan (unwrap! (get-loan loan-id) err-not-found))
      (borrower (get borrower loan))
      (lender (get lender loan))
      (total-repayment (unwrap! (calculate-repayment-amount loan-id) err-not-found))
      (new-repaid (+ (get repaid-amount loan) amount))
      (collateral-amt (get collateral loan))
    )
    (asserts! (is-eq tx-sender borrower) err-unauthorized)
    (asserts! (is-eq (get status loan) "active") err-loan-not-active)
    (asserts! (> amount u0) err-invalid-amount)
    
    ;; Transfer repayment to lender
    (try! (stx-transfer? amount tx-sender lender))
    
    ;; Check if fully repaid
    (if (>= new-repaid total-repayment)
      (begin
        ;; Loan fully repaid - return collateral
        (map-set loans { loan-id: loan-id } (merge loan { 
          repaid-amount: new-repaid,
          status: "repaid"
        }))
        
        ;; Return collateral from lender to borrower
        (if (> collateral-amt u0)
          (try! (stx-transfer? collateral-amt lender borrower))
          true
        )
        
        ;; Update stats
        (update-user-stats-repay borrower (get amount loan))
        (update-user-stats-lend-complete lender)
      )
      ;; Partial repayment
      (map-set loans { loan-id: loan-id } (merge loan { repaid-amount: new-repaid }))
    )
    
    (ok true)
  )
)

;; Liquidate defaulted loan
(define-public (liquidate-loan (loan-id uint))
  (let
    (
      (loan (unwrap! (get-loan loan-id) err-not-found))
      (lender (get lender loan))
    )
    (asserts! (is-eq tx-sender lender) err-unauthorized)
    (asserts! (is-eq (get status loan) "active") err-loan-not-active)
    (asserts! (unwrap! (is-loan-overdue loan-id) err-not-found) err-loan-active)
    
    ;; Update loan status (collateral already with lender)
    (map-set loans { loan-id: loan-id } (merge loan { status: "liquidated" }))
    
    ;; Update borrower stats (add default)
    (let
      (
        (borrower (get borrower loan))
        (stats (get-user-stats borrower))
      )
      (map-set user-stats
        { user: borrower }
        (merge stats { 
          defaults: (+ (get defaults stats) u1),
          active-loans-as-borrower: (- (get active-loans-as-borrower stats) u1)
        })
      )
    )
    
    (ok true)
  )
)

;; Private functions
(define-private (update-user-stats-borrow (user principal) (amount uint))
  (let
    (
      (stats (get-user-stats user))
    )
    (map-set user-stats
      { user: user }
      {
        total-borrowed: (+ (get total-borrowed stats) amount),
        total-lent: (get total-lent stats),
        active-loans-as-borrower: (+ (get active-loans-as-borrower stats) u1),
        active-loans-as-lender: (get active-loans-as-lender stats),
        total-repaid: (get total-repaid stats),
        defaults: (get defaults stats)
      }
    )
  )
)

(define-private (update-user-stats-lend (user principal) (amount uint))
  (let
    (
      (stats (get-user-stats user))
    )
    (map-set user-stats
      { user: user }
      {
        total-borrowed: (get total-borrowed stats),
        total-lent: (+ (get total-lent stats) amount),
        active-loans-as-borrower: (get active-loans-as-borrower stats),
        active-loans-as-lender: (+ (get active-loans-as-lender stats) u1),
        total-repaid: (get total-repaid stats),
        defaults: (get defaults stats)
      }
    )
  )
)

(define-private (update-user-stats-repay (user principal) (amount uint))
  (let
    (
      (stats (get-user-stats user))
    )
    (map-set user-stats
      { user: user }
      (merge stats {
        total-repaid: (+ (get total-repaid stats) amount),
        active-loans-as-borrower: (- (get active-loans-as-borrower stats) u1)
      })
    )
  )
)

(define-private (update-user-stats-lend-complete (user principal))
  (let
    (
      (stats (get-user-stats user))
    )
    (map-set user-stats
      { user: user }
      (merge stats {
        active-loans-as-lender: (- (get active-loans-as-lender stats) u1)
      })
    )
  )
)

Functions (11)

FunctionAccessArgs
get-loanread-onlyloan-id: uint
get-user-statsread-onlyuser: principal
calculate-repayment-amountread-onlyloan-id: uint
is-loan-overdueread-onlyloan-id: uint
create-loanpublicborrower: principal, amount: uint, collateral-amount: uint, interest-rate: uint, duration: uint, min-collateral-ratio: uint
repay-loanpublicloan-id: uint, amount: uint
liquidate-loanpublicloan-id: uint
update-user-stats-borrowprivateuser: principal, amount: uint
update-user-stats-lendprivateuser: principal, amount: uint
update-user-stats-repayprivateuser: principal, amount: uint
update-user-stats-lend-completeprivateuser: principal