;; beacon-base - Collateralized Lending Protocol
;; Borrow against collateral with dynamic interest rates
(define-constant CONTRACT-OWNER tx-sender)
(define-constant INTEREST-RATE u500)
(define-constant COLLATERAL-RATIO u150)
(define-constant LIQUIDATION-THRESHOLD u120)
(define-constant BLOCKS-PER-YEAR u52560)
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-INSUFFICIENT-COLLATERAL (err u101))
(define-constant ERR-NO-POSITION (err u102))
(define-constant ERR-HEALTHY (err u103))
(define-constant ERR-ZERO-AMOUNT (err u104))
(define-constant ERR-OVER-LIMIT (err u105))
(define-map positions principal
{ collateral: uint, debt: uint, last-update: uint, health: uint })
(define-map interest-checkpoints principal uint)
(define-data-var total-collateral uint u0)
(define-data-var total-debt uint u0)
(define-data-var utilization-rate uint u0)
(define-data-var protocol-reserves uint u0)
(define-private (calculate-interest (debt uint) (blocks uint))
(/ (* debt (* INTEREST-RATE blocks)) (* u10000 BLOCKS-PER-YEAR)))
(define-private (get-health-factor (collateral uint) (debt uint))
(if (is-eq debt u0) u999
(/ (* collateral u100) debt)))
(define-public (deposit-collateral (amount uint))
(begin
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
(match (map-get? positions tx-sender)
p (map-set positions tx-sender
(merge p { collateral: (+ (get collateral p) amount),
health: (get-health-factor (+ (get collateral p) amount) (get debt p)) }))
(map-set positions tx-sender
{ collateral: amount, debt: u0, last-update: block-height, health: u999 }))
(var-set total-collateral (+ (var-get total-collateral) amount))
(ok true)))
(define-public (borrow (amount uint))
(let ((position (unwrap! (map-get? positions tx-sender) ERR-NO-POSITION)))
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(let ((new-debt (+ (get debt position) amount))
(required-collateral (/ (* new-debt COLLATERAL-RATIO) u100)))
(asserts! (>= (get collateral position) required-collateral) ERR-INSUFFICIENT-COLLATERAL)
(map-set positions tx-sender
(merge position { debt: new-debt, last-update: block-height,
health: (get-health-factor (get collateral position) new-debt) }))
(var-set total-debt (+ (var-get total-debt) amount))
(as-contract (stx-transfer? amount tx-sender contract-caller)))))
(define-public (repay (amount uint))
(let ((position (unwrap! (map-get? positions tx-sender) ERR-NO-POSITION)))
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(let ((interest (calculate-interest (get debt position) (- block-height (get last-update position))))
(total-owed (+ (get debt position) interest))
(repay-amount (if (> amount total-owed) total-owed amount)))
(try! (stx-transfer? repay-amount tx-sender (as-contract tx-sender)))
(map-set positions tx-sender
(merge position { debt: (- total-owed repay-amount), last-update: block-height,
health: (get-health-factor (get collateral position) (- total-owed repay-amount)) }))
(ok true))))
(define-public (liquidate (borrower principal))
(let ((position (unwrap! (map-get? positions borrower) ERR-NO-POSITION)))
(asserts! (< (get health position) LIQUIDATION-THRESHOLD) ERR-HEALTHY)
(let ((debt (get debt position))
(collateral (get collateral position)))
(try! (stx-transfer? debt tx-sender (as-contract tx-sender)))
(map-delete positions borrower)
(var-set total-collateral (- (var-get total-collateral) collateral))
(var-set total-debt (- (var-get total-debt) debt))
(as-contract (stx-transfer? collateral tx-sender tx-sender)))))
(define-read-only (get-position (borrower principal)) (ok (map-get? positions borrower)))
(define-read-only (get-health-factor-of (borrower principal))
(match (map-get? positions borrower)
p (ok (get-health-factor (get collateral p) (get debt p)))
(ok u0)))
(define-read-only (get-protocol-stats)
(ok { total-collateral: (var-get total-collateral), total-debt: (var-get total-debt) }))