Source Code

;; Time Banking Protocol - Core Contract (Clarity 4)
;; Manages user registration, time credit balances, and core banking functionality
;; Refactored to use Clarity 4 features: stacks-block-time, improved type safety

;; Constants and Error Codes
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_UNAUTHORIZED (err u1001))
(define-constant ERR_INVALID_TIME (err u1002))
(define-constant ERR_INSUFFICIENT_BALANCE (err u1003))
(define-constant ERR_INVALID_PARAMS (err u1005))
(define-constant ERR_ALREADY_REGISTERED (err u1006))
(define-constant ERR_NOT_FOUND (err u1007))
(define-constant ERR_INSUFFICIENT_CREDITS (err u1010))
(define-constant ERR_SELF_TRANSFER (err u1011))
(define-constant ERR_USER_INACTIVE (err u1012))

;; Configuration Constants
(define-constant INITIAL_CREDITS u10) ;; New users get 10 hours of credits to start
(define-constant MAX_MINT_AMOUNT u1000) ;; Maximum credits that can be minted at once
(define-constant MIN_CREDIT_AMOUNT u1) ;; Minimum credit transfer amount

;; Data Structures using Clarity 4 best practices
(define-map users
    principal
    {
        joined-at: uint,              ;; Uses stacks-block-time
        total-hours-given: uint,
        total-hours-received: uint,
        reputation-score: uint,
        is-active: bool,
        last-activity: uint           ;; Track last activity time
    })

;; Time Credit Balances - Core of the time banking economy
(define-map time-balances
    principal
    uint) ;; Available time credits in hours

;; Variables
(define-data-var total-users uint u0)
(define-data-var total-credits-circulating uint u0)
(define-data-var min-transfer-amount uint u1)
(define-data-var protocol-paused bool false)

;; Events using native print for Clarity 4
(define-private (emit-user-registered (user principal) (credits uint))
    (print {
        event: "user-registered",
        user: user,
        initial-credits: credits,
        timestamp: stacks-block-time
    }))

(define-private (emit-credits-transferred (from principal) (to principal) (amount uint))
    (print {
        event: "credits-transferred",
        from: from,
        to: to,
        amount: amount,
        timestamp: stacks-block-time
    }))

(define-private (emit-credits-minted (recipient principal) (amount uint))
    (print {
        event: "credits-minted",
        recipient: recipient,
        amount: amount,
        timestamp: stacks-block-time
    }))

(define-private (emit-user-deactivated (user principal))
    (print {
        event: "user-deactivated",
        user: user,
        timestamp: stacks-block-time
    }))

;; Credit Management Functions
(define-read-only (get-balance (user principal))
    (default-to u0 (map-get? time-balances user)))

(define-private (add-credits-internal (user principal) (amount uint))
    (let ((current-balance (get-balance user)))
        (map-set time-balances user (+ current-balance amount))
        (var-set total-credits-circulating (+ (var-get total-credits-circulating) amount))
        true))

(define-private (deduct-credits-internal (user principal) (amount uint))
    (let ((current-balance (get-balance user)))
        (asserts! (>= current-balance amount) ERR_INSUFFICIENT_CREDITS)
        (map-set time-balances user (- current-balance amount))
        (var-set total-credits-circulating (- (var-get total-credits-circulating) amount))
        (ok true)))

;; Public function to transfer credits between users
(define-public (transfer-credits (to principal) (amount uint))
    (begin
        (asserts! (not (var-get protocol-paused)) ERR_UNAUTHORIZED)
        (asserts! (not (is-eq tx-sender to)) ERR_SELF_TRANSFER)
        (asserts! (>= amount (var-get min-transfer-amount)) ERR_INVALID_PARAMS)
        (asserts! (is-some (map-get? users tx-sender)) ERR_NOT_FOUND)
        (asserts! (is-some (map-get? users to)) ERR_NOT_FOUND)

        ;; Check both users are active
        (asserts! (get is-active (unwrap! (map-get? users tx-sender) ERR_NOT_FOUND)) ERR_USER_INACTIVE)
        (asserts! (get is-active (unwrap! (map-get? users to) ERR_NOT_FOUND)) ERR_USER_INACTIVE)

        ;; Perform the transfer
        (try! (deduct-credits-internal tx-sender amount))
        (add-credits-internal to amount)

        ;; Update activity timestamps
        (try! (update-last-activity tx-sender))
        (try! (update-last-activity to))

        (emit-credits-transferred tx-sender to amount)
        (ok true)))

;; Update user's last activity timestamp
(define-private (update-last-activity (user principal))
    (let ((user-data (unwrap! (map-get? users user) ERR_NOT_FOUND)))
        (map-set users user (merge user-data {
            last-activity: stacks-block-time
        }))
        (ok true)))

;; Administrative Functions
(define-public (set-min-transfer-amount (amount uint))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (asserts! (>= amount MIN_CREDIT_AMOUNT) ERR_INVALID_PARAMS)
        (var-set min-transfer-amount amount)
        (print {event: "config-updated", parameter: "min-transfer-amount", value: amount})
        (ok true)))

(define-public (toggle-protocol-pause)
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (var-set protocol-paused (not (var-get protocol-paused)))
        (print {event: "protocol-pause-toggled", paused: (var-get protocol-paused)})
        (ok true)))

;; Emergency credit functions (admin only)
(define-public (mint-credits (user principal) (amount uint))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (asserts! (not (var-get protocol-paused)) ERR_UNAUTHORIZED)
        (asserts! (is-some (map-get? users user)) ERR_NOT_FOUND)
        (asserts! (and (> amount u0) (<= amount MAX_MINT_AMOUNT)) ERR_INVALID_PARAMS)

        (add-credits-internal user amount)
        (emit-credits-minted user amount)
        (ok true)))

(define-public (burn-credits (user principal) (amount uint))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
        (asserts! (is-some (map-get? users user)) ERR_NOT_FOUND)
        (asserts! (> amount u0) ERR_INVALID_PARAMS)

        (try! (deduct-credits-internal user amount))
        (print {event: "credits-burned", user: user, amount: amount})
        (ok true)))

;; User Management
(define-public (register-user)
    (begin
        (asserts! (not (var-get protocol-paused)) ERR_UNAUTHORIZED)
        (asserts! (is-none (map-get? users tx-sender)) ERR_ALREADY_REGISTERED)

        ;; Create user profile with stacks-block-time
        (map-set users tx-sender {
            joined-at: stacks-block-time,
            total-hours-given: u0,
            total-hours-received: u0,
            reputation-score: u0,
            is-active: true,
            last-activity: stacks-block-time
        })

        ;; Give new users initial credits to participate in the economy
        (add-credits-internal tx-sender INITIAL_CREDITS)

        ;; Increment total users
        (var-set total-users (+ (var-get total-users) u1))

        (emit-user-registered tx-sender INITIAL_CREDITS)
        (ok true)))

(define-public (deactivate-user)
    (let ((user-data (unwrap! (map-get? users tx-sender) ERR_NOT_FOUND)))
        (asserts! (get is-active user-data) ERR_USER_INACTIVE)

        (map-set users tx-sender (merge user-data {
            is-active: false,
            last-activity: stacks-block-time
        }))

        (emit-user-deactivated tx-sender)
        (ok true)))

(define-public (reactivate-user)
    (let ((user-data (unwrap! (map-get? users tx-sender) ERR_NOT_FOUND)))
        (asserts! (not (get is-active user-data)) ERR_ALREADY_REGISTERED)

        (map-set users tx-sender (merge user-data {
            is-active: true,
            last-activity: stacks-block-time
        }))

        (print {event: "user-reactivated", user: tx-sender, timestamp: stacks-block-time})
        (ok true)))

;; Update user statistics (called by exchange contracts)
(define-public (update-user-stats (provider principal) (receiver principal) (hours uint))
    (let (
        (provider-data (unwrap! (map-get? users provider) ERR_NOT_FOUND))
        (receiver-data (unwrap! (map-get? users receiver) ERR_NOT_FOUND))
    )
        (map-set users provider (merge provider-data {
            total-hours-given: (+ (get total-hours-given provider-data) hours),
            last-activity: stacks-block-time
        }))
        (map-set users receiver (merge receiver-data {
            total-hours-received: (+ (get total-hours-received receiver-data) hours),
            last-activity: stacks-block-time
        }))
        (ok true)))

(define-public (update-reputation (user principal) (score-delta int))
    (let ((user-data (unwrap! (map-get? users user) ERR_NOT_FOUND)))
        (let ((current-score (to-int (get reputation-score user-data)))
              (new-score-int (+ current-score score-delta)))
            (asserts! (>= new-score-int 0) ERR_INVALID_PARAMS)
            (map-set users user (merge user-data {
                reputation-score: (to-uint new-score-int)
            }))
            (ok true))))

;; Read-Only Functions
(define-read-only (get-user-info (user principal))
    (map-get? users user))

(define-read-only (get-user-balance (user principal))
    (ok (get-balance user)))

(define-read-only (get-contract-owner)
    (ok CONTRACT_OWNER))

(define-read-only (is-user-active (user principal))
    (match (map-get? users user)
        user-data (ok (get is-active user-data))
        (ok false)))

(define-read-only (can-afford-service (user principal) (hours uint))
    (ok (>= (get-balance user) hours)))

(define-read-only (get-protocol-stats)
    (ok {
        total-users: (var-get total-users),
        total-credits-circulating: (var-get total-credits-circulating),
        initial-credits-per-user: INITIAL_CREDITS,
        min-transfer-amount: (var-get min-transfer-amount),
        protocol-paused: (var-get protocol-paused)
    }))

(define-read-only (get-user-activity-info (user principal))
    (match (map-get? users user)
        user-data (ok {
            last-activity: (get last-activity user-data),
            is-active: (get is-active user-data),
            hours-given: (get total-hours-given user-data),
            hours-received: (get total-hours-received user-data)
        })
        ERR_NOT_FOUND))

Functions (25)

FunctionAccessArgs
emit-user-registeredprivateuser: principal, credits: uint
emit-credits-transferredprivatefrom: principal, to: principal, amount: uint
emit-credits-mintedprivaterecipient: principal, amount: uint
emit-user-deactivatedprivateuser: principal
get-balanceread-onlyuser: principal
add-credits-internalprivateuser: principal, amount: uint
deduct-credits-internalprivateuser: principal, amount: uint
transfer-creditspublicto: principal, amount: uint
update-last-activityprivateuser: principal
set-min-transfer-amountpublicamount: uint
toggle-protocol-pausepublic
mint-creditspublicuser: principal, amount: uint
burn-creditspublicuser: principal, amount: uint
register-userpublic
deactivate-userpublic
reactivate-userpublic
update-user-statspublicprovider: principal, receiver: principal, hours: uint
update-reputationpublicuser: principal, score-delta: int
get-user-inforead-onlyuser: principal
get-user-balanceread-onlyuser: principal
get-contract-ownerread-only
is-user-activeread-onlyuser: principal
can-afford-serviceread-onlyuser: principal, hours: uint
get-protocol-statsread-only
get-user-activity-inforead-onlyuser: principal