Source Code

;; This smart contract manages a disaster-relief donation system with recipient allocations and admin controls.
;; It allows users to donate funds, administrators to manage recipients and contract settings,
;; and recipients to withdraw allocated funds.

;; constants
;;
(define-constant ERR_UNAUTHORIZED (err u1))
(define-constant ERR_INVALID_AMOUNT (err u2))
(define-constant ERR_INSUFFICIENT_FUNDS (err u3))
(define-constant ERR_RECIPIENT_NOT_FOUND (err u4))
(define-constant ERR_RECIPIENT_EXISTS (err u5))
(define-constant ERR_NOT_CONFIRMED (err u8))
(define-constant ERR_UNCHANGED_STATE (err u9))
(define-constant withdrawal-cooldown u86400) ;; 24 hours in seconds

;; data maps and vars
;;
(define-data-var total-funds uint u0)
(define-data-var admin principal tx-sender)
(define-data-var min-donation uint u1)
(define-data-var max-donation uint u1000000000)
(define-data-var paused bool false)

;; Maps
(define-map donations principal uint)
(define-map recipients principal uint)
(define-map last-withdrawal principal uint)

;; private functions
(define-private (distribute-to-recipient (recipient-data {recipient: principal, allocation: uint}))
  (let ((recipient (get recipient recipient-data))
        (allocation (get allocation recipient-data)))
    (try! (stx-transfer? allocation tx-sender recipient))
    (print {event: "funds-sent", recipient: recipient, amount: allocation})
    (ok true)
  )
)


;; public functions
;;
(define-public (donate (amount uint))
  (if (and (not (var-get paused))
           (>= amount (var-get min-donation))
           (<= amount (var-get max-donation)))
    (begin
      (map-set donations tx-sender (+ (get-donation tx-sender) amount))
      (var-set total-funds (+ (var-get total-funds) amount))
      (print {event: "donation", sender: tx-sender, amount: amount})
      (ok amount)
    )
    (err u100) ;; Error: Invalid donation amount or contract paused
  )
)

(define-public (add-recipient (recipient principal) (allocation uint))
  (if (and (is-eq tx-sender (var-get admin))
           (is-none (map-get? recipients recipient))
           (> allocation u0))
    (begin
      (map-set recipients recipient allocation)
      (print {event: "recipient-added", recipient: recipient, allocation: allocation})
      (ok (tuple (recipient recipient) (allocation allocation)))
    )
    (err u101) ;; Error: Unauthorized or invalid input
  )
)

(define-public (withdraw (amount uint))
  (let (
    (recipient-allocation (default-to u0 (map-get? recipients tx-sender)))
    (last-withdrawal-time (default-to u0 (map-get? last-withdrawal tx-sender)))
    (current-time stacks-block-time)
  )
    (if (and (not (var-get paused))
             (> recipient-allocation u0)
             (>= recipient-allocation amount)
             (>= (- current-time last-withdrawal-time) withdrawal-cooldown))
      (begin
        (map-set recipients tx-sender (- recipient-allocation amount))
        (var-set total-funds (- (var-get total-funds) amount))
        (map-set last-withdrawal tx-sender current-time)
        (print {event: "withdrawal", recipient: tx-sender, amount: amount})
        (stx-transfer? amount tx-sender tx-sender)
      )
      (err u102) ;; Error: Invalid withdrawal or cooldown period not met
    )
  )
)

(define-public (set-admin (new-admin principal))
  (let ((current-admin (var-get admin)))
    (begin
      (asserts! (is-eq tx-sender current-admin) ERR_UNAUTHORIZED)
      (asserts! (not (is-eq new-admin current-admin)) (err u6)) ;; New error for unchanged admin
      (var-set admin new-admin)
      (ok new-admin)
    )
  )
)

(define-public (set-donation-limits (new-min uint) (new-max uint))
  (if (and (is-eq tx-sender (var-get admin)) (< new-min new-max))
    (begin
      (var-set min-donation new-min)
      (var-set max-donation new-max)
      (print {event: "donation-limits-updated", min: new-min, max: new-max})
      (ok true)
    )
    (err u104) ;; Error: Unauthorized or invalid limits
  )
)

(define-public (set-paused (new-paused-state bool))
  (let ((current-paused-state (var-get paused)))
    (begin
      (asserts! (is-eq tx-sender (var-get admin)) ERR_UNAUTHORIZED)
      (asserts! (not (is-eq new-paused-state current-paused-state)) ERR_UNCHANGED_STATE)
      (var-set paused new-paused-state)
      (print {event: "contract-pause-changed", paused: new-paused-state})
      (ok new-paused-state)
    )
  )
)

(define-public (update-recipient-allocation (recipient principal) (new-allocation uint))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_UNAUTHORIZED)
    (asserts! (> new-allocation u0) ERR_INVALID_AMOUNT)
    (asserts! (is-some (map-get? recipients recipient)) ERR_RECIPIENT_NOT_FOUND)
    (ok (map-set recipients recipient new-allocation))
  )
)

(define-public (remove-recipient (recipient principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_UNAUTHORIZED)
    (asserts! (is-some (map-get? recipients recipient)) ERR_RECIPIENT_NOT_FOUND)
    (map-delete recipients recipient)
    (ok true)
  )
)

(define-public (confirm-remove-recipient (recipient principal) (confirm bool))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) ERR_UNAUTHORIZED)
    (asserts! confirm ERR_NOT_CONFIRMED)
    (asserts! (is-some (map-get? recipients recipient)) ERR_RECIPIENT_NOT_FOUND)
    (map-delete recipients recipient)
    (print {event: "recipient-removed", recipient: recipient})
    (ok true)
  )
)

(define-public (request-refund (amount uint))
  (let ((donation (get-donation tx-sender))
        (current-time stacks-block-time))
    (if (and (<= amount donation)
             (>= (- current-time stacks-block-time) withdrawal-cooldown)) ;; Refund within 24 hours
      (begin
        (map-set donations tx-sender (- donation amount))
        (var-set total-funds (- (var-get total-funds) amount))
        (stx-transfer? amount tx-sender tx-sender)
      )
      (err u110) ;; Error: Refund period expired or invalid amount
    )
  )
)

(define-public (emergency-shutdown)
  (if (is-eq tx-sender (var-get admin))
    (begin
      (var-set paused true)
      (print {event: "emergency-shutdown", admin: tx-sender})
      (ok true)
    )
    (err u105) ;; Error: Unauthorized
  )
)

(define-public (log-audit (action-type (string-ascii 32)) (actor principal))
  (let ((current-time stacks-block-time))
    (ok true)
  )
)


(define-read-only (get-donation (user principal))
  (default-to u0 (map-get? donations user))
)

(define-read-only (get-total-funds)
  (ok (var-get total-funds))
)

(define-read-only (is-paused)
  (ok (var-get paused))
)

(define-read-only (get-recipient-allocation (recipient principal))
  (ok (default-to u0 (map-get? recipients recipient)))
)

(define-read-only (get-admin)
  (ok (var-get admin))
)

(define-read-only (get-user-history (user principal))
  ;; Return donation and withdrawal records for a user
  (ok (tuple (donations (map-get? donations user)) (withdrawals (map-get? last-withdrawal user))))
)


Functions (19)

FunctionAccessArgs
distribute-to-recipientprivaterecipient-data: {recipient: principal, allocation: uint}
donatepublicamount: uint
add-recipientpublicrecipient: principal, allocation: uint
withdrawpublicamount: uint
set-adminpublicnew-admin: principal
set-donation-limitspublicnew-min: uint, new-max: uint
set-pausedpublicnew-paused-state: bool
update-recipient-allocationpublicrecipient: principal, new-allocation: uint
remove-recipientpublicrecipient: principal
confirm-remove-recipientpublicrecipient: principal, confirm: bool
request-refundpublicamount: uint
emergency-shutdownpublic
log-auditpublicaction-type: (string-ascii 32
get-donationread-onlyuser: principal
get-total-fundsread-only
is-pausedread-only
get-recipient-allocationread-onlyrecipient: principal
get-adminread-only
get-user-historyread-onlyuser: principal