Source Code

;; Crowdfunding Contract
;; Create campaigns and collect STX until goal is met

(define-constant err-not-owner (err u100))
(define-constant err-campaign-not-found (err u101))
(define-constant err-campaign-ended (err u102))
(define-constant err-goal-not-met (err u103))
(define-constant err-goal-already-met (err u104))
(define-constant err-no-contribution (err u105))

(define-data-var campaign-nonce uint u0)

(define-map campaigns
    uint
    {
        owner: principal,
        title: (string-ascii 50),
        description: (string-ascii 500),
        goal: uint,
        raised: uint,
        deadline: uint,
        claimed: bool
    }
)

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

;; Create a new campaign
(define-public (create-campaign (title (string-ascii 50)) (description (string-ascii 500)) (goal uint) (duration uint))
    (let
        (
            (campaign-id (var-get campaign-nonce))
        )
        (map-set campaigns campaign-id {
            owner: tx-sender,
            title: title,
            description: description,
            goal: goal,
            raised: u0,
            deadline: (+ block-height duration),
            claimed: false
        })
        (var-set campaign-nonce (+ campaign-id u1))
        (ok campaign-id)
    )
)

;; Contribute to a campaign
(define-public (contribute (campaign-id uint) (amount uint))
    (let
        (
            (campaign (unwrap! (map-get? campaigns campaign-id) err-campaign-not-found))
            (contribution-key {campaign-id: campaign-id, contributor: tx-sender})
            (existing-contribution (default-to u0 (map-get? contributions contribution-key)))
        )
        (asserts! (< block-height (get deadline campaign)) err-campaign-ended)
        (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
        (map-set contributions contribution-key (+ existing-contribution amount))
        (map-set campaigns campaign-id (merge campaign {raised: (+ (get raised campaign) amount)}))
        (ok true)
    )
)

;; Claim funds if goal is met
(define-public (claim-funds (campaign-id uint))
    (let
        (
            (campaign (unwrap! (map-get? campaigns campaign-id) err-campaign-not-found))
        )
        (asserts! (is-eq tx-sender (get owner campaign)) err-not-owner)
        (asserts! (>= (get raised campaign) (get goal campaign)) err-goal-not-met)
        (asserts! (not (get claimed campaign)) err-goal-already-met)
        (try! (as-contract (stx-transfer? (get raised campaign) tx-sender (get owner campaign))))
        (map-set campaigns campaign-id (merge campaign {claimed: true}))
        (ok true)
    )
)

;; Refund if campaign failed
(define-public (refund (campaign-id uint))
    (let
        (
            (campaign (unwrap! (map-get? campaigns campaign-id) err-campaign-not-found))
            (contribution-key {campaign-id: campaign-id, contributor: tx-sender})
            (contribution (unwrap! (map-get? contributions contribution-key) err-no-contribution))
        )
        (asserts! (>= block-height (get deadline campaign)) err-campaign-ended)
        (asserts! (< (get raised campaign) (get goal campaign)) err-goal-already-met)
        (try! (as-contract (stx-transfer? contribution tx-sender tx-sender)))
        (map-delete contributions contribution-key)
        (ok true)
    )
)

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

(define-read-only (get-contribution (campaign-id uint) (contributor principal))
    (default-to u0 (map-get? contributions {campaign-id: campaign-id, contributor: contributor}))
)

Functions (6)

FunctionAccessArgs
create-campaignpublictitle: (string-ascii 50
contributepubliccampaign-id: uint, amount: uint
claim-fundspubliccampaign-id: uint
refundpubliccampaign-id: uint
get-campaignread-onlycampaign-id: uint
get-contributionread-onlycampaign-id: uint, contributor: principal