Source Code

;; Crowdfund Platform - Milestone-based crowdfunding
;; Built for Stacks Builder Challenge by Marcus David

(define-constant contract-owner tx-sender)
(define-constant err-not-found (err u101))
(define-constant err-unauthorized (err u102))
(define-constant err-campaign-ended (err u103))
(define-constant err-goal-not-met (err u104))

(define-data-var next-campaign-id uint u1)

(define-map campaigns
  uint
  {
    creator: principal,
    title: (string-ascii 100),
    goal: uint,
    raised: uint,
    end-block: uint,
    withdrawn: bool,
    active: bool
  }
)

(define-map contributions { campaign-id: uint, contributor: principal } uint)

(define-read-only (get-campaign (campaign-id uint))
  (map-get? campaigns campaign-id)
)

(define-public (create-campaign (title (string-ascii 100)) (goal uint) (duration uint))
  (let ((campaign-id (var-get next-campaign-id)))
    (map-set campaigns campaign-id {
      creator: tx-sender,
      title: title,
      goal: goal,
      raised: u0,
      end-block: (+ stacks-block-height duration),
      withdrawn: false,
      active: true
    })
    (var-set next-campaign-id (+ campaign-id u1))
    (ok campaign-id)
  )
)

(define-public (contribute (campaign-id uint) (amount uint))
  (match (map-get? campaigns campaign-id)
    campaign
      (begin
        (asserts! (get active campaign) err-campaign-ended)
        (asserts! (<= stacks-block-height (get end-block campaign)) err-campaign-ended)
        (try! (stx-transfer-memo? amount tx-sender (as-contract tx-sender) 0x63726f776466756e6420636f6e747269627574696f6e))
        (map-set contributions { campaign-id: campaign-id, contributor: tx-sender }
          (+ (default-to u0 (map-get? contributions { campaign-id: campaign-id, contributor: tx-sender })) amount))
        (map-set campaigns campaign-id (merge campaign { raised: (+ (get raised campaign) amount) }))
        (ok true)
      )
    err-not-found
  )
)

(define-public (withdraw-funds (campaign-id uint))
  (match (map-get? campaigns campaign-id)
    campaign
      (begin
        (asserts! (is-eq tx-sender (get creator campaign)) err-unauthorized)
        (asserts! (> stacks-block-height (get end-block campaign)) err-campaign-ended)
        (asserts! (>= (get raised campaign) (get goal campaign)) err-goal-not-met)
        (asserts! (not (get withdrawn campaign)) err-unauthorized)
        (try! (as-contract (stx-transfer-memo? (get raised campaign) tx-sender (get creator campaign) 0x63726f776466756e6420776974686472617761)))
        (map-set campaigns campaign-id (merge campaign { withdrawn: true, active: false }))
        (ok true)
      )
    err-not-found
  )
)

(define-public (refund (campaign-id uint))
  (match (map-get? campaigns campaign-id)
    campaign
      (let ((contribution (default-to u0 (map-get? contributions { campaign-id: campaign-id, contributor: tx-sender }))))
        (asserts! (> stacks-block-height (get end-block campaign)) err-campaign-ended)
        (asserts! (< (get raised campaign) (get goal campaign)) err-unauthorized)
        (asserts! (> contribution u0) err-not-found)
        (try! (as-contract (stx-transfer-memo? contribution tx-sender tx-sender 0x63726f776466756e64207265667564)))
        (map-delete contributions { campaign-id: campaign-id, contributor: tx-sender })
        (ok true)
      )
    err-not-found
  )
)

Functions (5)

FunctionAccessArgs
get-campaignread-onlycampaign-id: uint
create-campaignpublictitle: (string-ascii 100
contributepubliccampaign-id: uint, amount: uint
withdraw-fundspubliccampaign-id: uint
refundpubliccampaign-id: uint