Source Code

;; title: stx-identity
;; version:
;; summary:
;; description:

;; title: stacks-blacklist
;; version:
;; summary:
;; description:
;; Blacklist Manager Contract

;; Error Codes
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-ALREADY-BLACKLISTED (err u101))
(define-constant ERR-NOT-BLACKLISTED (err u102))
(define-constant ERR-INVALID-ARGUMENT (err u103))
(define-constant ERR-BATCH-OPERATION-FAILED (err u104))
(define-constant ERR-ADMIN-ONLY (err u105))
(define-constant ERR-CANNOT-BLACKLIST-ADMIN (err u106))
(define-constant ERR-INVALID-TIME (err u107))
(define-constant ERR-EXPIRED-BLACKLIST (err u108))

;; Data Variables
(define-data-var contract-admin principal tx-sender)
(define-data-var backup-admin principal tx-sender)
(define-data-var blacklisted-address-count uint u0)
(define-data-var is-contract-active bool true)
(define-data-var last-updated uint stacks-block-height)

;; Maps
(define-map blacklisted-addresses principal 
  {
    is-blacklisted: bool,
    timestamp: uint,
    expiry: uint,
    blacklist-level: uint
  })
(define-map blacklisted-address-reasons principal (string-utf8 500))
(define-map admins principal bool)
(define-map blacklist-appeals principal 
  {
    status: (string-utf8 20),
    appeal-timestamp: uint,
    appeal-reason: (string-utf8 500)
  })

;; Read-Only Functions
(define-read-only (is-address-blacklisted (address principal))
  (match (map-get? blacklisted-addresses address)
    entry (and 
            (get is-blacklisted entry)
            (> (get expiry entry) stacks-block-height))
    false))

(define-read-only (get-blacklist-details (address principal))
  (map-get? blacklisted-addresses address))

(define-read-only (get-blacklist-reason-for-address (address principal))
  (default-to u"" (map-get? blacklisted-address-reasons address)))

(define-read-only (get-total-blacklisted-address-count)
  (var-get blacklisted-address-count))

(define-read-only (is-admin (address principal))
  (default-to false (map-get? admins address)))

(define-read-only (get-appeal-status (address principal))
  (map-get? blacklist-appeals address))

(define-read-only (get-contract-status)
  {
    is-active: (var-get is-contract-active),
    last-updated: (var-get last-updated),
    total-blacklisted: (var-get blacklisted-address-count)
  })

;; Private Functions
(define-private (is-authorized)
  (or (is-eq tx-sender (var-get contract-admin))
      (is-eq tx-sender (var-get backup-admin))
      (is-admin tx-sender)))

(define-private (calculate-expiry (expiry-blocks (optional uint)))
  (default-to (+ stacks-block-height u1000) expiry-blocks))

;; Public Functions
(define-public (set-contract-admin (new-admin principal))
  (begin 
    (asserts! (is-eq tx-sender (var-get contract-admin)) ERR-NOT-AUTHORIZED)
    (asserts! (not (is-eq new-admin (var-get contract-admin))) ERR-INVALID-ARGUMENT)
    (var-set contract-admin new-admin)
    (map-set admins new-admin true)
    (ok true)))

(define-public (set-backup-admin (new-backup-admin principal))
  (begin 
    (asserts! (is-eq tx-sender (var-get contract-admin)) ERR-NOT-AUTHORIZED)
    (asserts! (not (is-eq new-backup-admin (var-get backup-admin))) ERR-INVALID-ARGUMENT)
    (var-set backup-admin new-backup-admin)
    (map-set admins new-backup-admin true)
    (ok true)))

(define-public (add-admin (address principal))
  (begin 
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (not (is-admin address)) ERR-ALREADY-BLACKLISTED)
    (map-set admins address true)
    (ok true)))

(define-public (remove-admin (address principal))
  (begin 
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (not (is-eq address (var-get contract-admin))) ERR-NOT-AUTHORIZED)
    (map-delete admins address)
    (ok true)))

(define-public (add-address-to-blacklist 
    (address principal) 
    (reason (string-utf8 500))
    (blacklist-level uint)
    (expiry-blocks (optional uint)))
  (begin 
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (not (is-admin address)) ERR-CANNOT-BLACKLIST-ADMIN)
    (asserts! (not (is-address-blacklisted address)) ERR-ALREADY-BLACKLISTED)
    (asserts! (> (len reason) u0) ERR-INVALID-ARGUMENT)
    (asserts! (and (>= blacklist-level u1) (<= blacklist-level u10)) ERR-INVALID-ARGUMENT)
    (let ((safe-expiry (calculate-expiry expiry-blocks)))
      (asserts! (> safe-expiry stacks-block-height) ERR-INVALID-TIME)
      (map-set blacklisted-addresses address 
        {
          is-blacklisted: true,
          timestamp: stacks-block-height,
          expiry: safe-expiry,
          blacklist-level: blacklist-level
        })
      (map-set blacklisted-address-reasons address reason)
      (var-set blacklisted-address-count (+ (var-get blacklisted-address-count) u1))
      (var-set last-updated stacks-block-height)
      (ok true))))

(define-public (remove-address-from-blacklist (address principal))
  (begin 
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (is-address-blacklisted address) ERR-NOT-BLACKLISTED)
    (map-delete blacklisted-addresses address)
    (map-delete blacklisted-address-reasons address)
    (var-set blacklisted-address-count (- (var-get blacklisted-address-count) u1))
    (var-set last-updated stacks-block-height)
    (ok true)))

(define-public (submit-appeal (reason (string-utf8 500)))
  (begin
    (asserts! (is-address-blacklisted tx-sender) ERR-NOT-BLACKLISTED)
    (asserts! (> (len reason) u0) ERR-INVALID-ARGUMENT)
    (map-set blacklist-appeals tx-sender
      {
        status: u"pending",
        appeal-timestamp: stacks-block-height,
        appeal-reason: reason
      })
    (ok true)))

(define-public (process-appeal (address principal) (approved bool))
  (begin
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (is-address-blacklisted address) ERR-NOT-BLACKLISTED)
    (let ((appeal-data (unwrap! (map-get? blacklist-appeals address) ERR-NOT-BLACKLISTED)))
      (map-set blacklist-appeals address
        (merge appeal-data { status: (if approved u"approved" u"rejected") }))
      (if approved 
        (remove-address-from-blacklist address)
        (ok true)))))

(define-public (toggle-contract-status)
  (begin
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (var-set is-contract-active (not (var-get is-contract-active)))
    (ok true)))

(define-public (update-blacklist-expiry (address principal) (new-expiry uint))
  (begin
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (> new-expiry stacks-block-height) ERR-INVALID-TIME)
    (asserts! (is-address-blacklisted address) ERR-NOT-BLACKLISTED)
    (match (map-get? blacklisted-addresses address)
      entry (begin
              (map-set blacklisted-addresses address
                (merge entry { expiry: new-expiry }))
              (ok true))
      ERR-NOT-BLACKLISTED)))

;; Prevent STX transfer to the contract
(define-public (receive-stx)
  (err ERR-INVALID-ARGUMENT))

Functions (20)

FunctionAccessArgs
is-address-blacklistedread-onlyaddress: principal
get-blacklist-detailsread-onlyaddress: principal
get-blacklist-reason-for-addressread-onlyaddress: principal
get-total-blacklisted-address-countread-only
is-adminread-onlyaddress: principal
get-appeal-statusread-onlyaddress: principal
get-contract-statusread-only
is-authorizedprivate
calculate-expiryprivateexpiry-blocks: (optional uint
set-contract-adminpublicnew-admin: principal
set-backup-adminpublicnew-backup-admin: principal
add-adminpublicaddress: principal
remove-adminpublicaddress: principal
add-address-to-blacklistpublicaddress: principal, reason: (string-utf8 500
remove-address-from-blacklistpublicaddress: principal
submit-appealpublicreason: (string-utf8 500
process-appealpublicaddress: principal, approved: bool
toggle-contract-statuspublic
update-blacklist-expirypublicaddress: principal, new-expiry: uint
receive-stxpublic