Cerulean-Marketplace-Milestone-v1

SP2GW18TVQR75W1VT53HYGBRGKFRV5BFYNAF5SS5J

Source Code


;; title: milestone
;; version:
;; constants
;;
(define-constant ERR_NOT_FOUND (err u404))
(define-constant ERR_EXPIRED (err u409))
(define-constant ERR_LESSTHAN_ONE_MONTH (err u410))
(define-data-var contract-owner principal tx-sender)
(define-constant commission u25)
(define-constant ERR_NOT_ARTIST (err u405))
(define-constant ERR_NOT_CLIENT (err u406))
(define-constant ERR_ALREADY_ACCEPTED (err u407))
(define-constant ERR_COPLETED_GIG (err u408))
(define-constant NOT_ACCEPTED (err u411))
(define-constant ERR_NOT_REDEEMABLE (err u412))
(define-constant ERR_NOT_AUTHORIZED (err u413))

(define-constant days u14)


;; data vars
;;
(define-data-var gig-count uint u0)

;; data maps
;; create a new gig - id
;; from: client who creates the gig
;; to: artist
;; JOB: string, but one of these
;; period: number of blocks in which artist has to end the work after accepting it
;; remaining-amount: amount that keeps the record of how much amount is hold by the contract of specific gig
;; current-stage: tracks which milestone is running for a particular gig
;; milestones: tracks all data related to milestones
(define-map gig uint { 
  from: principal,
  to: principal,
  job: (string-ascii 30), 
  block-created: uint,
  block-accepted: uint,
  period: uint,
  remaining-amount: uint,
  current-stage: uint,
  milestones: { first-mile: {amount:uint, completed:bool}, second-mile: {amount:uint, completed:bool},
   third-mile: {amount:uint, completed:bool}, fourth-mile: {amount:uint, completed:bool}, fifth-mile: {amount:uint, completed:bool}}
   }
)
;; public functions
;;
;; This is the function called by the client to create gig where they should enter the amount based on milestones
(define-public (create-milestone-gig (artist principal) (period uint) (job-title (string-ascii 30))
 (first-milestone-amount uint) (second-milestone-amount uint) (third-milestone-amount uint)
 (fourth-milestone-amount uint) (fifth-milestone-amount uint))
    (let ((gig-id (increment-gig-count)) (total-amount (+ first-milestone-amount second-milestone-amount third-milestone-amount fourth-milestone-amount fifth-milestone-amount)))
    (asserts! (> period (* u30 u144)) ERR_LESSTHAN_ONE_MONTH)
    (try! (stx-transfer? total-amount tx-sender (as-contract tx-sender)))
    (map-set gig gig-id {from: tx-sender, to:artist,job: job-title, block-created: block-height,block-accepted: u1,period: period,current-stage: u0,remaining-amount: total-amount, milestones: {
        first-mile: {amount: first-milestone-amount, completed:false}, second-mile: {amount:second-milestone-amount, completed:false}, third-mile: {amount:third-milestone-amount, completed:false},
        fourth-mile: {amount:fourth-milestone-amount, completed:false}, fifth-mile: {amount:fifth-milestone-amount, completed:false}}})
    (ok gig-id)
    )
)

(define-private (increment-gig-count)
  (begin
    (var-set gig-count (+ (var-get gig-count) u1))
    (var-get gig-count)))


;; This is the function called by the creator to accept the milestone gig
(define-public (accept-gig (gig-id uint))
(begin 
    (asserts! (is-eq tx-sender (get to (unwrap! (map-get? gig gig-id) ERR_NOT_FOUND))) ERR_NOT_ARTIST)
    (asserts! (> (get remaining-amount (unwrap-panic (map-get? gig gig-id))) u0) ERR_COPLETED_GIG)
    (let ((gig-info (unwrap-panic (map-get? gig gig-id)))
          (get-milestones  (get milestones gig-info))
          (get-stage (get current-stage gig-info))
          (get-first-milestone (get first-mile get-milestones))
          (amount (get amount get-first-milestone))
          (fee (/ (* amount commission) u1000)) 
    )
      (asserts! (is-eq get-stage u0) ERR_ALREADY_ACCEPTED)
      (asserts! (<= block-height (+ (get block-created gig-info) (* u144 days))) ERR_EXPIRED)
      (as-contract (try! (stx-transfer? ( - amount fee) tx-sender (get to gig-info))))
      (as-contract (try! (stx-transfer? fee tx-sender (var-get contract-owner))))
      (map-set gig gig-id (merge gig-info {block-accepted: block-height,current-stage: u1,remaining-amount:(- (get remaining-amount gig-info) amount) ,milestones: (merge get-milestones {first-mile:{amount:amount, completed: true}})}))
      (ok gig-id))
)
)

;; This function helps creator to decline the gig
(define-public (decline-gig (gig-id uint)) 
  (begin 
    (asserts! (is-eq tx-sender (get to (unwrap! (map-get? gig gig-id) ERR_NOT_FOUND))) ERR_NOT_ARTIST)
    (let ((gig-info (unwrap-panic (map-get? gig gig-id)))
        (get-stage (get current-stage gig-info))
    ) 
      (asserts! (is-eq get-stage u0) ERR_ALREADY_ACCEPTED)
      (asserts! (<= block-height (+ (get block-created gig-info) (* u144 days))) ERR_EXPIRED)
      (try! (as-contract (stx-transfer? (get remaining-amount gig-info) tx-sender (get from gig-info))))
      (map-set gig gig-id (merge gig-info {remaining-amount: u0}))
      (ok gig-id))))


;; This is the function called by the client to mark milestone as complete and sends the milestone amount
(define-public (complete-milestone (gig-id uint))
  (let ((gig-info (unwrap! (map-get? gig gig-id) ERR_NOT_FOUND))
        (get-milestones  (get milestones gig-info))
        (get-milestone (latest-milestone gig-id))
        (total-amount (get remaining-amount gig-info))
        (amount (get amount get-milestone))
        (current-stage (get current-stage gig-info))
        (fee (/ (* amount commission) u1000)) 
      ) 
    (asserts! (is-eq tx-sender (get from (unwrap! (map-get? gig gig-id) ERR_NOT_FOUND))) ERR_NOT_CLIENT)
    (asserts! (and  (not (get completed get-milestone)) (> amount u0)) ERR_COPLETED_GIG)
    (asserts! (>= current-stage u1) NOT_ACCEPTED)
    (as-contract (try! (stx-transfer? ( - amount fee) tx-sender (get to gig-info))))
    (as-contract (try! (stx-transfer? fee tx-sender (var-get contract-owner))))
      (if (is-eq current-stage u1) 
        (ok (map-set gig gig-id (merge gig-info {current-stage: (+ (get current-stage gig-info) u1),remaining-amount:(- total-amount amount),milestones: (merge get-milestones {second-mile:{amount:amount, completed: true}})})))
        (if (is-eq current-stage u2)
          (ok (map-set gig gig-id (merge gig-info {current-stage: (+ (get current-stage gig-info) u1),remaining-amount:(- total-amount amount),milestones: (merge get-milestones {third-mile:{amount:amount, completed: true}})})))
          (if (is-eq current-stage u3)
            (ok (map-set gig gig-id (merge gig-info {current-stage: (+ (get current-stage gig-info) u1),remaining-amount:(- total-amount amount),milestones: (merge get-milestones {fourth-mile:{amount:amount, completed: true}})})))
            (ok (map-set gig gig-id (merge gig-info {current-stage: (+ (get current-stage gig-info) u1),remaining-amount:(- total-amount amount),milestones: (merge get-milestones {fifth-mile:{amount:amount, completed: true}})})))
      )
    ))
  )
)
;; This is the function called by the client or creator
;; If the client don't reedeem-fund after 5 days of end date then creator can redeem it and client don't

(define-public (redeem-fund (gig-id uint)) 
  (begin 
    (asserts! (is-some (map-get? gig gig-id)) ERR_NOT_FOUND)
    (let ((gig-info (unwrap-panic (map-get? gig gig-id)))) 
      (asserts! (or (is-eq tx-sender (get from gig-info)) (is-eq tx-sender (get to gig-info))) ERR_NOT_AUTHORIZED)
      (if (is-eq tx-sender (get from gig-info))
          (begin 
            (asserts! (unwrap-panic (can-client-redeem gig-id tx-sender)) ERR_NOT_REDEEMABLE)
            (try! (as-contract (stx-transfer? (get remaining-amount gig-info) tx-sender (get from gig-info))))
          )
          (let ((total-amount (get remaining-amount gig-info))
                (fee (/ (* total-amount commission) u1000))
              )
            (asserts! (unwrap-panic (can-creator-redeem gig-id tx-sender)) ERR_NOT_REDEEMABLE)       
            (try! (as-contract (stx-transfer? (- (get remaining-amount gig-info) fee) tx-sender (get to gig-info))))
            (as-contract (try! (stx-transfer? fee tx-sender (var-get contract-owner))))
          )
      )
      (ok (map-set gig gig-id (merge gig-info {remaining-amount: u0})))
    )
  )
)

;; read only functions
;;
(define-read-only (get-gig (gig-id uint)) 
  (ok (unwrap! (map-get? gig gig-id) ERR_NOT_FOUND)));; private functions
;;

;; if client who created the gig didn't collect his collateral after 5 days of end date then creator will collect it.
(define-read-only (can-creator-redeem (gig-id uint) (creator principal))
 (let ((gig-info (unwrap-panic (map-get? gig gig-id)))) 
    (if (and (and (and (is-eq (get to gig-info) creator) 
            (> (get remaining-amount gig-info) u0))
            (>  (get current-stage gig-info) u0)
        (> block-height (+ (+ (get block-accepted gig-info) (get period gig-info) (* u5 u144))))))
      (ok true)
      (ok false)
)))

;; redeem back funds as client if no answer in 7-14 days
(define-read-only (can-client-redeem (gig-id uint) (client principal))
 (let ((gig-info (unwrap-panic (map-get? gig gig-id)))) 
    (if (and 
      (and (is-eq (get from gig-info) client) (> (get remaining-amount gig-info) u0))
        (> block-height (+ (get block-created gig-info) (* u144 days))))
      (if (and (> block-height (+ (+ (get block-accepted gig-info) (get period gig-info) (* u5 u144))))
           (>  (get current-stage gig-info) u0)
      ) 
          (ok false) 
          (ok true))
      (ok false))))


(define-private (latest-milestone (gig-id uint))
 (let ((gig-info (unwrap-panic (map-get? gig gig-id)))
       (get-milestones  (get milestones gig-info))
       (get-stage (get current-stage gig-info))
      )
      (if (is-eq get-stage u1)
          (get second-mile get-milestones)
          (if (is-eq get-stage u2)
              (get third-mile get-milestones)
            (if (is-eq get-stage u3)
                (get fourth-mile get-milestones)
                (get fifth-mile get-milestones)
            )
          )
      )
  )
)
;; to be used to see if time period is expired
;; only for accepted
(define-read-only (check-is-expired (gig-id uint)) 
  (if (is-none (map-get? gig gig-id))
    false
    (let ((gig-info (unwrap-panic (map-get? gig gig-id)))
          (get-stage (get current-stage gig-info))
          (current-stage (get current-stage gig-info)))
      (if (and (>= current-stage u1) (> block-height (+ (get block-accepted gig-info) (get period gig-info))))
        true
        false))))

Functions (11)

FunctionAccessArgs
create-milestone-gigpublicartist: principal, period: uint, job-title: (string-ascii 30
increment-gig-countprivate
accept-gigpublicgig-id: uint
decline-gigpublicgig-id: uint
complete-milestonepublicgig-id: uint
redeem-fundpublicgig-id: uint
get-gigread-onlygig-id: uint
can-creator-redeemread-onlygig-id: uint, creator: principal
can-client-redeemread-onlygig-id: uint, client: principal
latest-milestoneprivategig-id: uint
check-is-expiredread-onlygig-id: uint