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-maturity-not-reached (err u105))
(define-constant err-already-repaid (err u106))
(define-constant err-insufficient-payment (err u107))
(define-constant err-defaulted (err u108))

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

(define-map debts
  uint
  {
    borrower: principal,
    lender: principal,
    principal-amount: uint,
    interest-rate: uint,
    outstanding-balance: uint,
    issue-block: uint,
    maturity-block: uint,
    repaid: bool,
    defaulted: bool
  }
)

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

(define-map payment-count uint uint)

(define-map borrower-debts principal (list 100 uint))
(define-map lender-debts principal (list 100 uint))

(define-public (issue-debt (lender principal) (principal-amount uint) (interest-rate uint) (maturity-block uint))
  (let
    (
      (debt-id (+ (var-get debt-nonce) u1))
      (borrower tx-sender)
      (current-height stacks-block-height)
    )
    (asserts! (> principal-amount u0) err-invalid-amount)
    (asserts! (> maturity-block current-height) err-invalid-amount)
    (map-set debts debt-id
      {
        borrower: borrower,
        lender: lender,
        principal-amount: principal-amount,
        interest-rate: interest-rate,
        outstanding-balance: principal-amount,
        issue-block: current-height,
        maturity-block: maturity-block,
        repaid: false,
        defaulted: false
      }
    )
    (map-set payment-count debt-id u0)
    (map-set borrower-debts borrower 
      (unwrap-panic (as-max-len? (append (default-to (list) (map-get? borrower-debts borrower)) debt-id) u100)))
    (map-set lender-debts lender
      (unwrap-panic (as-max-len? (append (default-to (list) (map-get? lender-debts lender)) debt-id) u100)))
    (var-set debt-nonce debt-id)
    (ok debt-id)
  )
)

(define-public (make-payment (debt-id uint) (amount uint))
  (let
    (
      (debt (unwrap! (map-get? debts debt-id) err-not-found))
      (borrower (get borrower debt))
      (current-balance (get outstanding-balance debt))
      (new-balance (if (>= amount current-balance) u0 (- current-balance amount)))
      (payment-id (+ (default-to u0 (map-get? payment-count debt-id)) u1))
    )
    (asserts! (is-eq tx-sender borrower) err-unauthorized)
    (asserts! (not (get repaid debt)) err-already-repaid)
    (asserts! (not (get defaulted debt)) err-defaulted)
    (asserts! (> amount u0) err-invalid-amount)
    (try! (stx-transfer? amount tx-sender (get lender debt)))
    (map-set payments {debt-id: debt-id, payment-id: payment-id}
      {
        amount: amount,
        block-height: stacks-block-height,
        payment-type: "payment"
      }
    )
    (map-set payment-count debt-id payment-id)
    (map-set debts debt-id (merge debt {
      outstanding-balance: new-balance,
      repaid: (is-eq new-balance u0)
    }))
    (ok true)
  )
)

(define-public (repay-full (debt-id uint))
  (let
    (
      (debt (unwrap! (map-get? debts debt-id) err-not-found))
      (borrower (get borrower debt))
      (total-due (calculate-total-due debt-id))
    )
    (asserts! (is-eq tx-sender borrower) err-unauthorized)
    (asserts! (not (get repaid debt)) err-already-repaid)
    (asserts! (not (get defaulted debt)) err-defaulted)
    (try! (stx-transfer? total-due tx-sender (get lender debt)))
    (let
      (
        (payment-id (+ (default-to u0 (map-get? payment-count debt-id)) u1))
      )
      (map-set payments {debt-id: debt-id, payment-id: payment-id}
        {
          amount: total-due,
          block-height: stacks-block-height,
          payment-type: "full-repayment"
        }
      )
      (map-set payment-count debt-id payment-id)
      (map-set debts debt-id (merge debt {
        outstanding-balance: u0,
        repaid: true
      }))
      (ok true)
    )
  )
)

(define-public (mark-default (debt-id uint))
  (let
    (
      (debt (unwrap! (map-get? debts debt-id) err-not-found))
    )
    (asserts! (is-eq tx-sender (get lender debt)) err-unauthorized)
    (asserts! (not (get repaid debt)) err-already-repaid)
    (asserts! (> stacks-block-height (get maturity-block debt)) err-maturity-not-reached)
    (asserts! (> (get outstanding-balance debt) u0) err-invalid-amount)
    (map-set debts debt-id (merge debt {defaulted: true}))
    (ok true)
  )
)

(define-read-only (get-debt (debt-id uint))
  (ok (map-get? debts debt-id))
)

(define-read-only (get-payment (debt-id uint) (payment-id uint))
  (ok (map-get? payments {debt-id: debt-id, payment-id: payment-id}))
)

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

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

(define-read-only (calculate-total-due (debt-id uint))
  (let
    (
      (debt (unwrap-panic (map-get? debts debt-id)))
      (principal (get outstanding-balance debt))
      (interest-rate (get interest-rate debt))
      (blocks-elapsed (- stacks-block-height (get issue-block debt)))
    )
    (+ principal (/ (* principal (* interest-rate blocks-elapsed)) u10000000))
  )
)

(define-read-only (get-debt-status (debt-id uint))
  (let
    (
      (debt (unwrap-panic (map-get? debts debt-id)))
    )
    (ok {
      repaid: (get repaid debt),
      defaulted: (get defaulted debt),
      overdue: (and (> stacks-block-height (get maturity-block debt)) (not (get repaid debt))),
      outstanding: (get outstanding-balance debt)
    })
  )
)

Functions (10)

FunctionAccessArgs
issue-debtpubliclender: principal, principal-amount: uint, interest-rate: uint, maturity-block: uint
make-paymentpublicdebt-id: uint, amount: uint
repay-fullpublicdebt-id: uint
mark-defaultpublicdebt-id: uint
get-debtread-onlydebt-id: uint
get-paymentread-onlydebt-id: uint, payment-id: uint
get-borrower-debtsread-onlyborrower: principal
get-lender-debtsread-onlylender: principal
calculate-total-dueread-onlydebt-id: uint
get-debt-statusread-onlydebt-id: uint