Source Code

;; Sadaqah Jariyah Contract (Ongoing Charity)
;; Continuous charity that keeps giving rewards
;; Halal - fulfills sadaqah jariyah principle
;; Clarity 4 compatible

(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u401))
(define-constant ERR-NOT-FOUND (err u404))

(define-data-var cause-count uint u0)
(define-data-var total-donated uint u0)

(define-map causes uint {
  name: (string-utf8 100), category: (string-utf8 50), guardian: principal,
  total-raised: uint, disbursed: uint, donors: uint, active: bool, created: uint
})
(define-map donor-records { cause-id: uint, donor: principal } { total: uint, last-donation: uint, frequency: uint })
(define-map disbursements { cause-id: uint, index: uint } { amount: uint, recipient: principal, purpose: (string-utf8 100), block: uint })
(define-map disbursement-count uint uint)

(define-public (create-cause (name (string-utf8 100)) (category (string-utf8 50)) (guardian principal))
  (let ((id (+ (var-get cause-count) u1)))
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (map-set causes id { name: name, category: category, guardian: guardian, total-raised: u0, disbursed: u0, donors: u0, active: true, created: stacks-block-height })
    (var-set cause-count id) (ok id)))

(define-public (donate (cause-id uint) (amount uint))
  (let (
    (cause (unwrap! (map-get? causes cause-id) ERR-NOT-FOUND))
    (prev (default-to { total: u0, last-donation: u0, frequency: u0 } (map-get? donor-records { cause-id: cause-id, donor: tx-sender })))
    (is-new (is-eq (get total prev) u0))
  )
    (asserts! (get active cause) ERR-NOT-FOUND)
    (try! (stx-transfer? amount tx-sender (get guardian cause)))
    (map-set donor-records { cause-id: cause-id, donor: tx-sender } { total: (+ (get total prev) amount), last-donation: stacks-block-height, frequency: (+ (get frequency prev) u1) })
    (map-set causes cause-id (merge cause { total-raised: (+ (get total-raised cause) amount), donors: (if is-new (+ (get donors cause) u1) (get donors cause)) }))
    (var-set total-donated (+ (var-get total-donated) amount))
    (print { event: "sadaqah-jariyah", donor: tx-sender, cause: cause-id, amount: amount })
    (ok amount)))

(define-public (disburse (cause-id uint) (recipient principal) (amount uint) (purpose (string-utf8 100)))
  (let (
    (cause (unwrap! (map-get? causes cause-id) ERR-NOT-FOUND))
    (idx (default-to u0 (map-get? disbursement-count cause-id)))
  )
    (asserts! (is-eq tx-sender (get guardian cause)) ERR-NOT-AUTHORIZED)
    (map-set disbursements { cause-id: cause-id, index: idx } { amount: amount, recipient: recipient, purpose: purpose, block: stacks-block-height })
    (map-set disbursement-count cause-id (+ idx u1))
    (map-set causes cause-id (merge cause { disbursed: (+ (get disbursed cause) amount) }))
    (ok true)))

(define-read-only (get-cause (id uint)) (map-get? causes id))
(define-read-only (get-donor (cause-id uint) (donor principal)) (map-get? donor-records { cause-id: cause-id, donor: donor }))
(define-read-only (get-disbursement (cause-id uint) (index uint)) (map-get? disbursements { cause-id: cause-id, index: index }))
(define-read-only (get-cause-count) (ok (var-get cause-count)))
(define-read-only (get-total-donated) (ok (var-get total-donated)))

Functions (8)

FunctionAccessArgs
create-causepublicname: (string-utf8 100
donatepubliccause-id: uint, amount: uint
disbursepubliccause-id: uint, recipient: principal, amount: uint, purpose: (string-utf8 100
get-causeread-onlyid: uint
get-donorread-onlycause-id: uint, donor: principal
get-disbursementread-onlycause-id: uint, index: uint
get-cause-countread-only
get-total-donatedread-only