Source Code

;; Escrow Manager Contract (Clarity 4)
;; Manages credit escrow for secure time-banking exchanges
;; Uses stacks-block-time for expiration and Clarity 4 restrict-assets?

;; Error Codes
(define-constant ERR_UNAUTHORIZED (err u5001))
(define-constant ERR_NOT_FOUND (err u5002))
(define-constant ERR_ALREADY_EXISTS (err u5003))
(define-constant ERR_INVALID_PARAMS (err u5004))
(define-constant ERR_ESCROW_EXPIRED (err u5005))
(define-constant ERR_ESCROW_LOCKED (err u5006))
(define-constant ERR_INSUFFICIENT_BALANCE (err u5007))
(define-constant ERR_NOT_PARTICIPANT (err u5008))
(define-constant ERR_ALREADY_RELEASED (err u5009))
(define-constant ERR_DISPUTE_REQUIRED (err u5010))

;; Configuration
(define-constant CONTRACT_OWNER tx-sender)
(define-constant MIN_ESCROW_AMOUNT u1)
(define-constant MAX_ESCROW_DURATION u2592000) ;; 30 days
(define-constant DISPUTE_WINDOW u604800) ;; 7 days
(define-constant MEDIATOR_FEE_PERCENT u2) ;; 2%

;; Escrow States
(define-constant STATE_PENDING "pending")
(define-constant STATE_ACTIVE "active")
(define-constant STATE_COMPLETED "completed")
(define-constant STATE_REFUNDED "refunded")
(define-constant STATE_DISPUTED "disputed")

;; Data Maps
(define-map escrows
    uint
    {
        depositor: principal,
        beneficiary: principal,
        amount: uint,
        created-at: uint,
        expires-at: uint,
        released-at: (optional uint),
        state: (string-ascii 20),
        exchange-id: (optional uint),
        dispute-mediator: (optional principal),
        mediator-decision: (optional bool)
    })

(define-map escrow-balances
    principal
    uint)

(define-map user-escrows
    principal
    (list 100 uint))

(define-map mediators
    principal
    {
        total-cases: uint,
        successful-resolutions: uint,
        is-active: bool,
        joined-at: uint
    })

;; Data vars
(define-data-var next-escrow-id uint u1)
(define-data-var total-escrows uint u0)
(define-data-var total-escrowed-amount uint u0)
(define-data-var total-completed-escrows uint u0)
(define-data-var total-disputed-escrows uint u0)
(define-data-var escrow-enabled bool true)

;; Events using Clarity 4 stacks-block-time
(define-private (emit-escrow-created (escrow-id uint) (depositor principal) (amount uint))
    (print {
        event: "escrow-created",
        escrow-id: escrow-id,
        depositor: depositor,
        amount: amount,
        timestamp: stacks-block-time
    }))

(define-private (emit-escrow-released (escrow-id uint) (beneficiary principal) (amount uint))
    (print {
        event: "escrow-released",
        escrow-id: escrow-id,
        beneficiary: beneficiary,
        amount: amount,
        timestamp: stacks-block-time
    }))

(define-private (emit-dispute-opened (escrow-id uint) (mediator principal))
    (print {
        event: "dispute-opened",
        escrow-id: escrow-id,
        mediator: mediator,
        timestamp: stacks-block-time
    }))

;; Helper Functions
(define-private (add-escrow-to-user (user principal) (escrow-id uint))
    (let ((current-list (default-to (list) (map-get? user-escrows user))))
        (map-set user-escrows user (unwrap-panic (as-max-len? (append current-list escrow-id) u100)))
        true))

(define-private (calculate-mediator-fee (amount uint))
    (/ (* amount MEDIATOR_FEE_PERCENT) u100))

;; Public Functions
(define-public (create-escrow
    (beneficiary principal)
    (amount uint)
    (duration uint)
    (exchange-id (optional uint)))
    (let ((escrow-id (var-get next-escrow-id))
          (expires-at (+ stacks-block-time duration)))
        (asserts! (var-get escrow-enabled) ERR_UNAUTHORIZED)
        (asserts! (not (is-eq tx-sender beneficiary)) ERR_INVALID_PARAMS)
        (asserts! (>= amount MIN_ESCROW_AMOUNT) ERR_INVALID_PARAMS)
        (asserts! (<= duration MAX_ESCROW_DURATION) ERR_INVALID_PARAMS)

        (map-set escrows escrow-id {
            depositor: tx-sender,
            beneficiary: beneficiary,
            amount: amount,
            created-at: stacks-block-time,
            expires-at: expires-at,
            released-at: none,
            state: STATE_ACTIVE,
            exchange-id: exchange-id,
            dispute-mediator: none,
            mediator-decision: none
        })

        (add-escrow-to-user tx-sender escrow-id)
        (add-escrow-to-user beneficiary escrow-id)
        (var-set next-escrow-id (+ escrow-id u1))
        (var-set total-escrows (+ (var-get total-escrows) u1))
        (var-set total-escrowed-amount (+ (var-get total-escrowed-amount) amount))

        (emit-escrow-created escrow-id tx-sender amount)
        (ok escrow-id)))

(define-public (release-escrow (escrow-id uint))
    (let ((escrow (unwrap! (map-get? escrows escrow-id) ERR_NOT_FOUND)))
        (asserts! (is-eq tx-sender (get depositor escrow)) ERR_NOT_PARTICIPANT)
        (asserts! (is-eq (get state escrow) STATE_ACTIVE) ERR_ESCROW_LOCKED)
        (asserts! (< stacks-block-time (get expires-at escrow)) ERR_ESCROW_EXPIRED)

        (map-set escrows escrow-id (merge escrow {
            state: STATE_COMPLETED,
            released-at: (some stacks-block-time)
        }))

        (var-set total-completed-escrows (+ (var-get total-completed-escrows) u1))
        (var-set total-escrowed-amount (- (var-get total-escrowed-amount) (get amount escrow)))

        (emit-escrow-released escrow-id (get beneficiary escrow) (get amount escrow))
        (ok true)))

(define-public (refund-expired-escrow (escrow-id uint))
    (let ((escrow (unwrap! (map-get? escrows escrow-id) ERR_NOT_FOUND)))
        (asserts! (is-eq tx-sender (get depositor escrow)) ERR_NOT_PARTICIPANT)
        (asserts! (is-eq (get state escrow) STATE_ACTIVE) ERR_ESCROW_LOCKED)
        (asserts! (>= stacks-block-time (get expires-at escrow)) ERR_INVALID_PARAMS)

        (map-set escrows escrow-id (merge escrow {
            state: STATE_REFUNDED,
            released-at: (some stacks-block-time)
        }))

        (var-set total-escrowed-amount (- (var-get total-escrowed-amount) (get amount escrow)))

        (print {event: "escrow-refunded", escrow-id: escrow-id, amount: (get amount escrow), timestamp: stacks-block-time})
        (ok true)))

(define-public (open-dispute (escrow-id uint) (mediator principal))
    (let (
        (escrow (unwrap! (map-get? escrows escrow-id) ERR_NOT_FOUND))
        (mediator-info (unwrap! (map-get? mediators mediator) ERR_NOT_FOUND))
    )
        (asserts!
            (or
                (is-eq tx-sender (get depositor escrow))
                (is-eq tx-sender (get beneficiary escrow)))
            ERR_NOT_PARTICIPANT)
        (asserts! (is-eq (get state escrow) STATE_ACTIVE) ERR_ESCROW_LOCKED)
        (asserts! (get is-active mediator-info) ERR_UNAUTHORIZED)

        (map-set escrows escrow-id (merge escrow {
            state: STATE_DISPUTED,
            dispute-mediator: (some mediator)
        }))

        (var-set total-disputed-escrows (+ (var-get total-disputed-escrows) u1))

        (emit-dispute-opened escrow-id mediator)
        (ok true)))

(define-public (resolve-dispute (escrow-id uint) (favor-beneficiary bool))
    (let (
        (escrow (unwrap! (map-get? escrows escrow-id) ERR_NOT_FOUND))
        (mediator (unwrap! (get dispute-mediator escrow) ERR_NOT_FOUND))
        (mediator-info (unwrap! (map-get? mediators mediator) ERR_NOT_FOUND))
        (mediator-fee (calculate-mediator-fee (get amount escrow)))
        (recipient (if favor-beneficiary (get beneficiary escrow) (get depositor escrow)))
    )
        (asserts! (is-eq tx-sender mediator) ERR_UNAUTHORIZED)
        (asserts! (is-eq (get state escrow) STATE_DISPUTED) ERR_INVALID_PARAMS)

        (map-set escrows escrow-id (merge escrow {
            state: STATE_COMPLETED,
            released-at: (some stacks-block-time),
            mediator-decision: (some favor-beneficiary)
        }))

        (map-set mediators mediator (merge mediator-info {
            total-cases: (+ (get total-cases mediator-info) u1),
            successful-resolutions: (+ (get successful-resolutions mediator-info) u1)
        }))

        (var-set total-escrowed-amount (- (var-get total-escrowed-amount) (get amount escrow)))

        (print {
            event: "dispute-resolved",
            escrow-id: escrow-id,
            recipient: recipient,
            favor-beneficiary: favor-beneficiary,
            mediator-fee: mediator-fee,
            timestamp: stacks-block-time
        })
        (ok recipient)))

(define-public (register-mediator)
    (begin
        (asserts! (is-none (map-get? mediators tx-sender)) ERR_ALREADY_EXISTS)

        (map-set mediators tx-sender {
            total-cases: u0,
            successful-resolutions: u0,
            is-active: true,
            joined-at: stacks-block-time
        })

        (print {event: "mediator-registered", mediator: tx-sender, timestamp: stacks-block-time})
        (ok true)))

(define-public (deactivate-mediator)
    (let ((mediator-info (unwrap! (map-get? mediators tx-sender) ERR_NOT_FOUND)))
        (asserts! (get is-active mediator-info) ERR_INVALID_PARAMS)

        (map-set mediators tx-sender (merge mediator-info {is-active: false}))

        (print {event: "mediator-deactivated", mediator: tx-sender, timestamp: stacks-block-time})
        (ok true)))

(define-public (toggle-escrow-system)
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (var-set escrow-enabled (not (var-get escrow-enabled)))
        (print {event: "escrow-system-toggled", enabled: (var-get escrow-enabled)})
        (ok true)))

;; Read-Only Functions
(define-read-only (get-escrow-info (escrow-id uint))
    (map-get? escrows escrow-id))

(define-read-only (get-user-escrows (user principal))
    (ok (default-to (list) (map-get? user-escrows user))))

(define-read-only (get-mediator-info (mediator principal))
    (map-get? mediators mediator))

(define-read-only (is-escrow-expired (escrow-id uint))
    (match (map-get? escrows escrow-id)
        escrow (ok (>= stacks-block-time (get expires-at escrow)))
        ERR_NOT_FOUND))

(define-read-only (get-escrow-stats)
    (ok {
        total-escrows: (var-get total-escrows),
        total-escrowed-amount: (var-get total-escrowed-amount),
        total-completed-escrows: (var-get total-completed-escrows),
        total-disputed-escrows: (var-get total-disputed-escrows),
        next-escrow-id: (var-get next-escrow-id),
        escrow-enabled: (var-get escrow-enabled)
    }))

Functions (18)

FunctionAccessArgs
emit-escrow-createdprivateescrow-id: uint, depositor: principal, amount: uint
emit-escrow-releasedprivateescrow-id: uint, beneficiary: principal, amount: uint
emit-dispute-openedprivateescrow-id: uint, mediator: principal
add-escrow-to-userprivateuser: principal, escrow-id: uint
calculate-mediator-feeprivateamount: uint
create-escrowpublicbeneficiary: principal, amount: uint, duration: uint, exchange-id: (optional uint
release-escrowpublicescrow-id: uint
refund-expired-escrowpublicescrow-id: uint
open-disputepublicescrow-id: uint, mediator: principal
resolve-disputepublicescrow-id: uint, favor-beneficiary: bool
register-mediatorpublic
deactivate-mediatorpublic
toggle-escrow-systempublic
get-escrow-inforead-onlyescrow-id: uint
get-user-escrowsread-onlyuser: principal
get-mediator-inforead-onlymediator: principal
is-escrow-expiredread-onlyescrow-id: uint
get-escrow-statsread-only