Source Code

;; Clicker Contract v2m - Enhanced with validation, admin controls, and improved events
;; Contract 1 of 3 for Stacks Transaction Hub

;; ============================================
;; CONTRACT METADATA
;; ============================================
(define-constant CONTRACT-NAME "clicker")
(define-constant VERSION u5)

;; ============================================
;; CONFIGURATION
;; ============================================
(define-constant contract-owner tx-sender)
(define-constant interaction-fee u1000) ;; 0.001 STX = 1000 microSTX
(define-constant MAX-MULTI-CLICK u100)

;; ============================================
;; ERROR CODES
;; ============================================
;; Fee errors (100-109)
(define-constant ERR-INSUFFICIENT-FEE (err u100))
(define-constant ERR-TRANSFER-FAILED (err u101))

;; Authorization errors (110-119)
(define-constant ERR-NOT-OWNER (err u110))
(define-constant ERR-UNAUTHORIZED (err u111))

;; State errors (120-129)
(define-constant ERR-CONTRACT-PAUSED (err u120))

;; Input validation errors (130-139)
(define-constant ERR-INVALID-COUNT (err u130))
(define-constant ERR-ZERO-VALUE (err u131))

;; ============================================
;; DATA VARIABLES
;; ============================================
(define-data-var total-clicks uint u0)
(define-data-var total-fees-collected uint u0)
(define-data-var last-clicker (optional principal) none)
(define-data-var last-activity-block uint u0)
(define-data-var unique-users uint u0)
(define-data-var is-paused bool false)

;; ============================================
;; DATA MAPS
;; ============================================
(define-map user-clicks principal uint)
(define-map user-streaks principal uint)
(define-map user-first-click principal uint)

;; ============================================
;; PRIVATE FUNCTIONS
;; ============================================

;; Check if contract is active
(define-private (check-not-paused)
  (ok (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED))
)

;; Check if caller is owner
(define-private (check-owner)
  (ok (asserts! (is-eq tx-sender contract-owner) ERR-NOT-OWNER))
)

;; Collect interaction fee
(define-private (collect-fee)
  (begin
    (try! (stx-transfer? interaction-fee tx-sender contract-owner))
    (var-set total-fees-collected (+ (var-get total-fees-collected) interaction-fee))
    (ok true)
  )
)

;; Track new user
(define-private (track-new-user)
  (if (is-eq (default-to u0 (map-get? user-clicks tx-sender)) u0)
    (begin
      (var-set unique-users (+ (var-get unique-users) u1))
      (map-set user-first-click tx-sender block-height)
      true
    )
    false
  )
)

;; ============================================
;; READ-ONLY FUNCTIONS
;; ============================================

(define-read-only (get-version)
  VERSION
)

(define-read-only (get-contract-name)
  CONTRACT-NAME
)

(define-read-only (get-total-clicks)
  (var-get total-clicks)
)

(define-read-only (get-total-fees-collected)
  (var-get total-fees-collected)
)

(define-read-only (get-interaction-fee)
  interaction-fee
)

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

(define-read-only (get-user-streak (user principal))
  (default-to u0 (map-get? user-streaks user))
)

(define-read-only (get-last-clicker)
  (var-get last-clicker)
)

(define-read-only (get-unique-users)
  (var-get unique-users)
)

(define-read-only (get-last-activity-block)
  (var-get last-activity-block)
)

(define-read-only (get-user-first-click (user principal))
  (map-get? user-first-click user)
)

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

(define-read-only (get-stats (user principal))
  {
    total: (var-get total-clicks),
    user-clicks: (get-user-clicks user),
    streak: (get-user-streak user),
    last-clicker: (var-get last-clicker),
    fees-collected: (var-get total-fees-collected),
    paused: (var-get is-paused)
  }
)

(define-read-only (get-contract-info)
  {
    name: CONTRACT-NAME,
    version: VERSION,
    total-clicks: (var-get total-clicks),
    unique-users: (var-get unique-users),
    fee: interaction-fee,
    last-activity: (var-get last-activity-block),
    owner: contract-owner,
    paused: (var-get is-paused)
  }
)

;; ============================================
;; PUBLIC FUNCTIONS
;; ============================================

;; Simple click - costs 0.001 STX
(define-public (click)
  (let
    (
      (current-clicks (get-user-clicks tx-sender))
      (current-streak (get-user-streak tx-sender))
      (new-count (+ current-clicks u1))
      (new-total (+ (var-get total-clicks) u1))
    )
    ;; Check contract is active
    (try! (check-not-paused))
    ;; Track new user before fee
    (track-new-user)
    ;; Collect fee
    (try! (collect-fee))
    ;; Update state
    (map-set user-clicks tx-sender new-count)
    (map-set user-streaks tx-sender (+ current-streak u1))
    (var-set total-clicks new-total)
    (var-set last-clicker (some tx-sender))
    (var-set last-activity-block block-height)
    ;; Emit enhanced event
    (print {
      event: "click",
      version: VERSION,
      user: tx-sender,
      user-total: new-count,
      global-total: new-total,
      block: block-height
    })
    (ok new-count)
  )
)

;; Multi-click - costs 0.001 STX (capped at 100)
(define-public (multi-click (count uint))
  (let
    (
      (current-clicks (get-user-clicks tx-sender))
      (safe-count (if (> count MAX-MULTI-CLICK) MAX-MULTI-CLICK count))
      (new-count (+ current-clicks safe-count))
      (new-total (+ (var-get total-clicks) safe-count))
    )
    ;; Check contract is active
    (try! (check-not-paused))
    ;; Validate input
    (asserts! (> count u0) ERR-ZERO-VALUE)
    ;; Track new user
    (track-new-user)
    ;; Collect fee
    (try! (collect-fee))
    ;; Update state
    (map-set user-clicks tx-sender new-count)
    (var-set total-clicks new-total)
    (var-set last-clicker (some tx-sender))
    (var-set last-activity-block block-height)
    ;; Emit enhanced event
    (print {
      event: "multi-click",
      version: VERSION,
      user: tx-sender,
      added: safe-count,
      user-total: new-count,
      global-total: new-total,
      block: block-height
    })
    (ok safe-count)
  )
)

;; Reset streak - costs 0.001 STX
(define-public (reset-streak)
  (begin
    (try! (check-not-paused))
    (try! (collect-fee))
    (map-set user-streaks tx-sender u0)
    (var-set last-activity-block block-height)
    (print {
      event: "reset-streak",
      version: VERSION,
      user: tx-sender,
      block: block-height
    })
    (ok true)
  )
)

;; Ping - costs 0.001 STX
(define-public (ping)
  (begin
    (try! (check-not-paused))
    (try! (collect-fee))
    (var-set last-activity-block block-height)
    (print {
      event: "ping",
      version: VERSION,
      user: tx-sender,
      block: block-height
    })
    (ok block-height)
  )
)

;; ============================================
;; ADMIN FUNCTIONS
;; ============================================

;; Pause contract (owner only)
(define-public (pause)
  (begin
    (try! (check-owner))
    (var-set is-paused true)
    (print {
      event: "contract-paused",
      version: VERSION,
      by: tx-sender,
      block: block-height
    })
    (ok true)
  )
)

;; Unpause contract (owner only)
(define-public (unpause)
  (begin
    (try! (check-owner))
    (var-set is-paused false)
    (print {
      event: "contract-unpaused",
      version: VERSION,
      by: tx-sender,
      block: block-height
    })
    (ok true)
  )
)

Functions (24)

FunctionAccessArgs
check-not-pausedprivate
check-ownerprivate
collect-feeprivate
track-new-userprivate
get-versionread-only
get-contract-nameread-only
get-total-clicksread-only
get-total-fees-collectedread-only
get-interaction-feeread-only
get-user-clicksread-onlyuser: principal
get-user-streakread-onlyuser: principal
get-last-clickerread-only
get-unique-usersread-only
get-last-activity-blockread-only
get-user-first-clickread-onlyuser: principal
is-contract-pausedread-only
get-statsread-onlyuser: principal
get-contract-inforead-only
clickpublic
multi-clickpubliccount: uint
reset-streakpublic
pingpublic
pausepublic
unpausepublic