Source Code

;; title: stx-sla
;; version:
;; summary:
;; description:

;; Service Agreement Smart Contract
;; Implements a service agreement between a service provider and client
;; with payment escrow, dispute resolution, and milestone tracking

;; Constants
(define-constant contract-administrator tx-sender)
(define-constant agreement-status-awaiting-payment u0)
(define-constant agreement-status-active u1)
(define-constant agreement-status-delivered u2)
(define-constant agreement-status-terminated u3)
(define-constant agreement-status-under-dispute u4)

;; Error constants
(define-constant ERROR_UNAUTHORIZED_ACCESS (err u100))
(define-constant ERROR_INVALID_AGREEMENT_STATUS (err u101))
(define-constant ERROR_INSUFFICIENT_PAYMENT (err u102))
(define-constant ERROR_AGREEMENT_ALREADY_EXISTS (err u103))
(define-constant ERROR_AGREEMENT_NOT_FOUND (err u104))
(define-constant ERROR_INVALID_MILESTONE_INDEX (err u105))
(define-constant ERROR_INVALID_INPUT (err u106))
(define-constant ERROR_INVALID_SERVICE_PROVIDER (err u107))
(define-constant ERROR_INVALID_MILESTONE_DATA (err u108))

;; Data structures
(define-map service-agreement-details
  { agreement-identifier: uint }
  {
    service-provider-address: principal,
    client-address: principal,
    total-service-cost: uint,
    agreement-status: uint,
    agreement-start-block: uint,
    agreement-end-block: uint,
    dispute-filing-deadline-block: uint,
    service-milestones: (list
      5
      {
        milestone-description: (string-utf8 100),
        milestone-payment: uint,
        milestone-completed: bool,
      }
    ),
  }
)

(define-map agreement-payment-escrow
  { agreement-identifier: uint }
  { escrowed-amount: uint }
)

(define-map agreement-disputes
  { agreement-identifier: uint }
  {
    dispute-reason: (string-utf8 200),
    dispute-initiator: principal,
    dispute-resolution: (optional (string-utf8 200)),
  }
)

;; Read-only functions
(define-read-only (get-agreement-details (agreement-identifier uint))
  (map-get? service-agreement-details { agreement-identifier: agreement-identifier })
)

(define-read-only (get-escrowed-payment (agreement-identifier uint))
  (default-to { escrowed-amount: u0 }
    (map-get? agreement-payment-escrow { agreement-identifier: agreement-identifier })
  )
)

(define-read-only (get-dispute-details (agreement-identifier uint))
  (map-get? agreement-disputes { agreement-identifier: agreement-identifier })
)

;; Private functions
(define-private (verify-participant-authorization (agreement-identifier uint))
  (let ((agreement-info (unwrap! (get-agreement-details agreement-identifier) false)))
    (or
      (is-eq tx-sender contract-administrator)
      (is-eq tx-sender (get service-provider-address agreement-info))
      (is-eq tx-sender (get client-address agreement-info))
    )
  )
)

(define-private (milestone-completed? (milestone {
  milestone-description: (string-utf8 100),
  milestone-payment: uint,
  milestone-completed: bool,
}))
  (get milestone-completed milestone)
)

(define-private (verify-all-milestones-complete (service-milestones (list
  5
  {
    milestone-description: (string-utf8 100),
    milestone-payment: uint,
    milestone-completed: bool,
  }
)))
  (and
    (milestone-completed? (unwrap-panic (element-at service-milestones u0)))
    (milestone-completed? (unwrap-panic (element-at service-milestones u1)))
    (milestone-completed? (unwrap-panic (element-at service-milestones u2)))
    (milestone-completed? (unwrap-panic (element-at service-milestones u3)))
    (milestone-completed? (unwrap-panic (element-at service-milestones u4)))
  )
)

(define-private (validate-milestone-payments
    (milestones (list
      5
      {
        milestone-description: (string-utf8 100),
        milestone-payment: uint,
        milestone-completed: bool,
      }
    ))
    (total-cost uint)
  )
  (let ((total-milestone-payments (+ (get milestone-payment (unwrap-panic (element-at milestones u0)))
      (get milestone-payment (unwrap-panic (element-at milestones u1)))
      (get milestone-payment (unwrap-panic (element-at milestones u2)))
      (get milestone-payment (unwrap-panic (element-at milestones u3)))
      (get milestone-payment (unwrap-panic (element-at milestones u4)))
    )))
    (and
      (is-eq total-milestone-payments total-cost) ;; Sum of milestone payments must equal total cost
      (>
        (len (get milestone-description (unwrap-panic (element-at milestones u0))))
        u0
      )
      ;; Validate descriptions
      (>
        (len (get milestone-description (unwrap-panic (element-at milestones u1))))
        u0
      )
      (>
        (len (get milestone-description (unwrap-panic (element-at milestones u2))))
        u0
      )
      (>
        (len (get milestone-description (unwrap-panic (element-at milestones u3))))
        u0
      )
      (>
        (len (get milestone-description (unwrap-panic (element-at milestones u4))))
        u0
      )
    )
  )
)

(define-private (update-milestone-at-index
    (milestone {
      milestone-description: (string-utf8 100),
      milestone-payment: uint,
      milestone-completed: bool,
    })
    (target-index uint)
    (index uint)
  )
  {
    milestone-description: (get milestone-description milestone),
    milestone-payment: (get milestone-payment milestone),
    milestone-completed: (if (is-eq index target-index)
      true
      (get milestone-completed milestone)
    ),
  }
)

(define-public (mark-milestone-complete
    (agreement-identifier uint)
    (milestone-index uint)
  )
  (let ((agreement-info (unwrap! (get-agreement-details agreement-identifier)
      ERROR_AGREEMENT_NOT_FOUND
    )))
    (asserts! (is-eq tx-sender (get service-provider-address agreement-info))
      ERROR_UNAUTHORIZED_ACCESS
    )
    (asserts!
      (is-eq (get agreement-status agreement-info) agreement-status-active)
      ERROR_INVALID_AGREEMENT_STATUS
    )
    (asserts! (< milestone-index (len (get service-milestones agreement-info)))
      ERROR_INVALID_MILESTONE_INDEX
    )

    (let (
        (milestones (get service-milestones agreement-info))
        (updated-service-milestones (list
          (update-milestone-at-index (unwrap-panic (element-at milestones u0))
            milestone-index u0
          )
          (update-milestone-at-index (unwrap-panic (element-at milestones u1))
            milestone-index u1
          )
          (update-milestone-at-index (unwrap-panic (element-at milestones u2))
            milestone-index u2
          )
          (update-milestone-at-index (unwrap-panic (element-at milestones u3))
            milestone-index u3
          )
          (update-milestone-at-index (unwrap-panic (element-at milestones u4))
            milestone-index u4
          )
        ))
      )
      (map-set service-agreement-details { agreement-identifier: agreement-identifier }
        (merge agreement-info { service-milestones: updated-service-milestones })
      )

      (if (verify-all-milestones-complete updated-service-milestones)
        (map-set service-agreement-details { agreement-identifier: agreement-identifier }
          (merge agreement-info {
            agreement-status: agreement-status-delivered,
            service-milestones: updated-service-milestones,
          })
        )
        true
      )

      (ok true)
    )
  )
)

(define-public (initiate-dispute
    (agreement-identifier uint)
    (dispute-reason (string-utf8 200))
  )
  (let ((agreement-info (unwrap! (get-agreement-details agreement-identifier)
      ERROR_AGREEMENT_NOT_FOUND
    )))
    (asserts! (verify-participant-authorization agreement-identifier)
      ERROR_UNAUTHORIZED_ACCESS
    )
    (asserts!
      (< stacks-block-height (get dispute-filing-deadline-block agreement-info))
      ERROR_INVALID_AGREEMENT_STATUS
    )
    (asserts! (> (len dispute-reason) u0) ERROR_INVALID_INPUT)

    (map-set agreement-disputes { agreement-identifier: agreement-identifier } {
      dispute-reason: dispute-reason,
      dispute-initiator: tx-sender,
      dispute-resolution: none,
    })

    (map-set service-agreement-details { agreement-identifier: agreement-identifier }
      (merge agreement-info { agreement-status: agreement-status-under-dispute })
    )

    (ok true)
  )
)

Functions (5)

FunctionAccessArgs
get-agreement-detailsread-onlyagreement-identifier: uint
get-escrowed-paymentread-onlyagreement-identifier: uint
get-dispute-detailsread-onlyagreement-identifier: uint
verify-participant-authorizationprivateagreement-identifier: uint
initiate-disputepublicagreement-identifier: uint, dispute-reason: (string-utf8 200