Source Code

;; Service Agreement Contract
;; On-chain service level agreements
;; Halal - fair contracts
;; Clarity 4 compatible

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-NOT-FOUND (err u404))
(define-constant ERR-ALREADY-SIGNED (err u405))

(define-data-var agreement-count uint u0)

(define-map agreements uint {
  service-provider: principal, client: principal, terms: (string-utf8 200),
  fee: uint, duration: uint, start-block: uint,
  provider-signed: bool, client-signed: bool, status: (string-ascii 20)
})
(define-map agreement-reviews uint { reviewer: principal, rating: uint, comment: (string-utf8 200), block: uint })

(define-public (create-agreement (client principal) (terms (string-utf8 200)) (fee uint) (duration uint))
  (let ((id (+ (var-get agreement-count) u1)))
    (map-set agreements id {
      service-provider: tx-sender, client: client, terms: terms,
      fee: fee, duration: duration, start-block: u0,
      provider-signed: true, client-signed: false, status: "draft"
    })
    (var-set agreement-count id) (ok id)))

(define-public (sign-agreement (agreement-id uint))
  (let ((agr (unwrap! (map-get? agreements agreement-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get client agr)) ERR-NOT-AUTHORIZED)
    (asserts! (not (get client-signed agr)) ERR-ALREADY-SIGNED)
    (try! (stx-transfer? (get fee agr) tx-sender CONTRACT-OWNER))
    (map-set agreements agreement-id (merge agr { client-signed: true, start-block: stacks-block-height, status: "active" }))
    (ok true)))

(define-public (complete-agreement (agreement-id uint))
  (let ((agr (unwrap! (map-get? agreements agreement-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get client agr)) ERR-NOT-AUTHORIZED)
    (asserts! (is-eq (get status agr) "active") ERR-NOT-FOUND)
    (try! (stx-transfer? (get fee agr) CONTRACT-OWNER (get service-provider agr)))
    (map-set agreements agreement-id (merge agr { status: "completed" }))
    (ok true)))

(define-public (dispute-agreement (agreement-id uint))
  (let ((agr (unwrap! (map-get? agreements agreement-id) ERR-NOT-FOUND)))
    (asserts! (or (is-eq tx-sender (get client agr)) (is-eq tx-sender (get service-provider agr))) ERR-NOT-AUTHORIZED)
    (map-set agreements agreement-id (merge agr { status: "disputed" })) (ok true)))

(define-public (resolve-dispute (agreement-id uint) (pay-provider bool))
  (let ((agr (unwrap! (map-get? agreements agreement-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (if pay-provider
      (begin (try! (stx-transfer? (get fee agr) CONTRACT-OWNER (get service-provider agr)))
        (map-set agreements agreement-id (merge agr { status: "resolved-provider" })))
      (begin (try! (stx-transfer? (get fee agr) CONTRACT-OWNER (get client agr)))
        (map-set agreements agreement-id (merge agr { status: "resolved-client" }))))
    (ok true)))

(define-public (leave-review (agreement-id uint) (rating uint) (comment (string-utf8 200)))
  (let ((agr (unwrap! (map-get? agreements agreement-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get client agr)) ERR-NOT-AUTHORIZED)
    (map-set agreement-reviews agreement-id { reviewer: tx-sender, rating: rating, comment: comment, block: stacks-block-height })
    (ok true)))

(define-read-only (get-agreement (id uint)) (map-get? agreements id))
(define-read-only (get-review (id uint)) (map-get? agreement-reviews id))
(define-read-only (get-agreement-count) (ok (var-get agreement-count)))

Functions (9)

FunctionAccessArgs
create-agreementpublicclient: principal, terms: (string-utf8 200
sign-agreementpublicagreement-id: uint
complete-agreementpublicagreement-id: uint
dispute-agreementpublicagreement-id: uint
resolve-disputepublicagreement-id: uint, pay-provider: bool
leave-reviewpublicagreement-id: uint, rating: uint, comment: (string-utf8 200
get-agreementread-onlyid: uint
get-reviewread-onlyid: uint
get-agreement-countread-only