Source Code

;; Eid Gift Contract
;; Eid gift exchange and distribution
;; Halal - celebrating Eid with generosity
;; 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-CLAIMED (err u405))

(define-data-var campaign-count uint u0)
(define-data-var total-gifts uint u0)

(define-map campaigns uint { organizer: principal, eid-type: (string-ascii 20), budget: uint, spent: uint, recipients: uint, max-recipients: uint, gift-amount: uint, status: (string-ascii 20) })
(define-map gift-recipients { campaign-id: uint, recipient: principal } { amount: uint, claimed: uint })
(define-map campaign-donors { campaign-id: uint, donor: principal } uint)

(define-public (create-campaign (eid-type (string-ascii 20)) (budget uint) (max-recipients uint) (gift-amount uint))
  (let ((id (+ (var-get campaign-count) u1)))
    (try! (stx-transfer? budget tx-sender CONTRACT-OWNER))
    (map-set campaigns id { organizer: tx-sender, eid-type: eid-type, budget: budget, spent: u0, recipients: u0, max-recipients: max-recipients, gift-amount: gift-amount, status: "active" })
    (var-set campaign-count id) (ok id)))

(define-public (donate-to-campaign (campaign-id uint) (amount uint))
  (let (
    (camp (unwrap! (map-get? campaigns campaign-id) ERR-NOT-FOUND))
    (prev (default-to u0 (map-get? campaign-donors { campaign-id: campaign-id, donor: tx-sender })))
  )
    (try! (stx-transfer? amount tx-sender CONTRACT-OWNER))
    (map-set campaign-donors { campaign-id: campaign-id, donor: tx-sender } (+ prev amount))
    (map-set campaigns campaign-id (merge camp { budget: (+ (get budget camp) amount) })) (ok amount)))

(define-public (distribute-gift (campaign-id uint) (recipient principal))
  (let ((camp (unwrap! (map-get? campaigns campaign-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get organizer camp)) ERR-NOT-AUTHORIZED)
    (asserts! (is-none (map-get? gift-recipients { campaign-id: campaign-id, recipient: recipient })) ERR-ALREADY-CLAIMED)
    (asserts! (< (get recipients camp) (get max-recipients camp)) ERR-NOT-FOUND)
    (try! (stx-transfer? (get gift-amount camp) CONTRACT-OWNER recipient))
    (map-set gift-recipients { campaign-id: campaign-id, recipient: recipient } { amount: (get gift-amount camp), claimed: stacks-block-height })
    (map-set campaigns campaign-id (merge camp { spent: (+ (get spent camp) (get gift-amount camp)), recipients: (+ (get recipients camp) u1) }))
    (var-set total-gifts (+ (var-get total-gifts) u1)) (ok true)))

(define-public (close-campaign (campaign-id uint))
  (let (
    (camp (unwrap! (map-get? campaigns campaign-id) ERR-NOT-FOUND))
    (remaining (- (get budget camp) (get spent camp)))
  )
    (asserts! (is-eq tx-sender (get organizer camp)) ERR-NOT-AUTHORIZED)
    (if (> remaining u0) (try! (stx-transfer? remaining CONTRACT-OWNER tx-sender)) true)
    (map-set campaigns campaign-id (merge camp { status: "closed" })) (ok remaining)))

(define-read-only (get-campaign (id uint)) (map-get? campaigns id))
(define-read-only (get-recipient (campaign-id uint) (who principal)) (map-get? gift-recipients { campaign-id: campaign-id, recipient: who }))
(define-read-only (get-campaign-count) (ok (var-get campaign-count)))
(define-read-only (get-total-gifts) (ok (var-get total-gifts)))

Functions (8)

FunctionAccessArgs
create-campaignpubliceid-type: (string-ascii 20
donate-to-campaignpubliccampaign-id: uint, amount: uint
distribute-giftpubliccampaign-id: uint, recipient: principal
close-campaignpubliccampaign-id: uint
get-campaignread-onlyid: uint
get-recipientread-onlycampaign-id: uint, who: principal
get-campaign-countread-only
get-total-giftsread-only