Source Code

;; Freelance Payment Contract
;; Milestone-based payments for freelance work
;; Halal - fair work compensation
;; 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-DONE (err u405))
(define-constant ERR-NOT-CLIENT (err u406))
(define-constant ERR-NOT-FREELANCER (err u407))

(define-data-var project-count uint u0)
(define-data-var total-paid uint u0)

(define-map projects uint {
  client: principal, freelancer: principal, title: (string-utf8 100),
  total-budget: uint, paid: uint, milestones: uint, completed: uint, status: (string-ascii 20)
})
(define-map milestones { project-id: uint, index: uint } { description: (string-utf8 200), amount: uint, completed: bool, approved: bool })

(define-public (create-project (freelancer principal) (title (string-utf8 100)) (budget uint) (milestone-count uint))
  (let ((id (+ (var-get project-count) u1)))
    (map-set projects id {
      client: tx-sender, freelancer: freelancer, title: title,
      total-budget: budget, paid: u0, milestones: milestone-count, completed: u0, status: "active"
    })
    (var-set project-count id) (ok id)))

(define-public (add-milestone (project-id uint) (index uint) (description (string-utf8 200)) (amount uint))
  (let ((project (unwrap! (map-get? projects project-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get client project)) ERR-NOT-CLIENT)
    (map-set milestones { project-id: project-id, index: index } { description: description, amount: amount, completed: false, approved: false })
    (ok true)))

(define-public (complete-milestone (project-id uint) (index uint))
  (let (
    (project (unwrap! (map-get? projects project-id) ERR-NOT-FOUND))
    (ms (unwrap! (map-get? milestones { project-id: project-id, index: index }) ERR-NOT-FOUND))
  )
    (asserts! (is-eq tx-sender (get freelancer project)) ERR-NOT-FREELANCER)
    (asserts! (not (get completed ms)) ERR-ALREADY-DONE)
    (map-set milestones { project-id: project-id, index: index } (merge ms { completed: true }))
    (ok true)))

(define-public (approve-and-pay (project-id uint) (index uint))
  (let (
    (project (unwrap! (map-get? projects project-id) ERR-NOT-FOUND))
    (ms (unwrap! (map-get? milestones { project-id: project-id, index: index }) ERR-NOT-FOUND))
  )
    (asserts! (is-eq tx-sender (get client project)) ERR-NOT-CLIENT)
    (asserts! (get completed ms) ERR-NOT-FOUND)
    (asserts! (not (get approved ms)) ERR-ALREADY-DONE)
    (try! (stx-transfer? (get amount ms) tx-sender (get freelancer project)))
    (map-set milestones { project-id: project-id, index: index } (merge ms { approved: true }))
    (map-set projects project-id (merge project { paid: (+ (get paid project) (get amount ms)), completed: (+ (get completed project) u1) }))
    (var-set total-paid (+ (var-get total-paid) (get amount ms)))
    (ok true)))

(define-read-only (get-project (id uint)) (map-get? projects id))
(define-read-only (get-milestone (project-id uint) (index uint)) (map-get? milestones { project-id: project-id, index: index }))
(define-read-only (get-project-count) (ok (var-get project-count)))
(define-read-only (get-total-paid) (ok (var-get total-paid)))

Functions (8)

FunctionAccessArgs
create-projectpublicfreelancer: principal, title: (string-utf8 100
add-milestonepublicproject-id: uint, index: uint, description: (string-utf8 200
complete-milestonepublicproject-id: uint, index: uint
approve-and-paypublicproject-id: uint, index: uint
get-projectread-onlyid: uint
get-milestoneread-onlyproject-id: uint, index: uint
get-project-countread-only
get-total-paidread-only