Source Code

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

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

;; ============================================
;; CONFIGURATION
;; ============================================
(define-constant contract-owner tx-sender)
(define-constant interaction-fee u1000) ;; 0.001 STX = 1000 microSTX
(define-constant MIN-TIP u1000) ;; Minimum tip: 0.001 STX
(define-constant MAX-TIP u100000000000) ;; Maximum tip: 100,000 STX

;; ============================================
;; ERROR CODES
;; ============================================
;; Fee/Transfer errors (100-109)
(define-constant ERR-INSUFFICIENT-BALANCE (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))
(define-constant ERR-SELF-TIP (err u112))

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

;; Input validation errors (130-139)
(define-constant ERR-INVALID-AMOUNT (err u130))
(define-constant ERR-ZERO-VALUE (err u131))
(define-constant ERR-TIP-TOO-SMALL (err u132))
(define-constant ERR-TIP-TOO-LARGE (err u133))
(define-constant ERR-INVALID-RECIPIENT (err u134))

;; ============================================
;; DATA VARIABLES
;; ============================================
(define-data-var total-tips uint u0)
(define-data-var tip-count uint u0)
(define-data-var total-fees-collected uint u0)
(define-data-var last-activity-block uint u0)
(define-data-var unique-tippers uint u0)
(define-data-var largest-tip uint u0)
(define-data-var is-paused bool false)

;; ============================================
;; DATA MAPS
;; ============================================
(define-map user-tips-sent principal uint)
(define-map user-tips-received principal uint)
(define-map user-tip-count principal uint)
(define-map user-first-tip 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)
  )
)

;; Validate tip amount
(define-private (validate-tip-amount (amount uint))
  (begin
    (asserts! (> amount u0) ERR-ZERO-VALUE)
    (asserts! (>= amount MIN-TIP) ERR-TIP-TOO-SMALL)
    (asserts! (<= amount MAX-TIP) ERR-TIP-TOO-LARGE)
    (ok true)
  )
)

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

;; Track largest tip
(define-private (track-largest-tip (amount uint))
  (if (> amount (var-get largest-tip))
    (begin
      (var-set largest-tip amount)
      true
    )
    false
  )
)

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

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

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

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

(define-read-only (get-tip-count)
  (var-get tip-count)
)

(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-tips-sent (user principal))
  (default-to u0 (map-get? user-tips-sent user))
)

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

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

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

(define-read-only (get-largest-tip)
  (var-get largest-tip)
)

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

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

(define-read-only (get-stats)
  {
    total-tips: (var-get total-tips),
    tip-count: (var-get tip-count),
    fees-collected: (var-get total-fees-collected),
    largest-tip: (var-get largest-tip),
    paused: (var-get is-paused)
  }
)

(define-read-only (get-contract-info)
  {
    name: CONTRACT-NAME,
    version: VERSION,
    total-tips: (var-get total-tips),
    tip-count: (var-get tip-count),
    unique-tippers: (var-get unique-tippers),
    largest-tip: (var-get largest-tip),
    fee: interaction-fee,
    min-tip: MIN-TIP,
    max-tip: MAX-TIP,
    last-activity: (var-get last-activity-block),
    owner: contract-owner,
    paused: (var-get is-paused)
  }
)

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

;; Tip the owner - send additional STX + 0.001 STX fee
(define-public (tip (amount uint))
  (let
    (
      (current-sent (get-user-tips-sent tx-sender))
      (current-received (get-user-tips-received contract-owner))
      (current-tip-count (get-user-tip-count tx-sender))
      (new-tip-count (+ (var-get tip-count) u1))
      (new-total-tips (+ (var-get total-tips) amount))
    )
    ;; Check contract is active
    (try! (check-not-paused))
    ;; Validate tip amount
    (try! (validate-tip-amount amount))
    ;; Track new tipper
    (track-new-tipper)
    ;; Collect fee first
    (try! (collect-fee))
    ;; Transfer tip
    (try! (stx-transfer? amount tx-sender contract-owner))
    ;; Update state
    (map-set user-tips-sent tx-sender (+ current-sent amount))
    (map-set user-tips-received contract-owner (+ current-received amount))
    (map-set user-tip-count tx-sender (+ current-tip-count u1))
    (var-set total-tips new-total-tips)
    (var-set tip-count new-tip-count)
    (var-set last-activity-block block-height)
    (track-largest-tip amount)
    ;; Emit enhanced event
    (print {
      event: "tip",
      version: VERSION,
      from: tx-sender,
      to: contract-owner,
      amount: amount,
      total-tips: new-total-tips,
      tip-number: new-tip-count,
      block: block-height
    })
    (ok amount)
  )
)

;; Tip a specific user - send STX + 0.001 STX fee
(define-public (tip-user (recipient principal) (amount uint))
  (let
    (
      (current-sent (get-user-tips-sent tx-sender))
      (current-received (get-user-tips-received recipient))
      (current-tip-count (get-user-tip-count tx-sender))
      (new-tip-count (+ (var-get tip-count) u1))
      (new-total-tips (+ (var-get total-tips) amount))
    )
    ;; Check contract is active
    (try! (check-not-paused))
    ;; Validate recipient
    (asserts! (not (is-eq tx-sender recipient)) ERR-SELF-TIP)
    ;; Validate tip amount
    (try! (validate-tip-amount amount))
    ;; Track new tipper
    (track-new-tipper)
    ;; Collect fee first
    (try! (collect-fee))
    ;; Transfer tip
    (try! (stx-transfer? amount tx-sender recipient))
    ;; Update state
    (map-set user-tips-sent tx-sender (+ current-sent amount))
    (map-set user-tips-received recipient (+ current-received amount))
    (map-set user-tip-count tx-sender (+ current-tip-count u1))
    (var-set total-tips new-total-tips)
    (var-set tip-count new-tip-count)
    (var-set last-activity-block block-height)
    (track-largest-tip amount)
    ;; Emit enhanced event
    (print {
      event: "tip-user",
      version: VERSION,
      from: tx-sender,
      to: recipient,
      amount: amount,
      total-tips: new-total-tips,
      tip-number: new-tip-count,
      block: block-height
    })
    (ok amount)
  )
)

;; Quick tip (fixed small amount) - 0.01 STX + 0.001 STX fee
(define-public (quick-tip)
  (let
    (
      (tip-amount u10000) ;; 0.01 STX
      (current-sent (get-user-tips-sent tx-sender))
      (current-received (get-user-tips-received contract-owner))
      (current-tip-count (get-user-tip-count tx-sender))
      (new-tip-count (+ (var-get tip-count) u1))
      (new-total-tips (+ (var-get total-tips) tip-amount))
    )
    ;; Check contract is active
    (try! (check-not-paused))
    ;; Track new tipper
    (track-new-tipper)
    ;; Collect fee first
    (try! (collect-fee))
    ;; Transfer tip
    (try! (stx-transfer? tip-amount tx-sender contract-owner))
    ;; Update state
    (map-set user-tips-sent tx-sender (+ current-sent tip-amount))
    (map-set user-tips-received contract-owner (+ current-received tip-amount))
    (map-set user-tip-count tx-sender (+ current-tip-count u1))
    (var-set total-tips new-total-tips)
    (var-set tip-count new-tip-count)
    (var-set last-activity-block block-height)
    ;; Emit enhanced event
    (print {
      event: "quick-tip",
      version: VERSION,
      from: tx-sender,
      to: contract-owner,
      amount: tip-amount,
      total-tips: new-total-tips,
      block: block-height
    })
    (ok tip-amount)
  )
)

;; 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 (27)

FunctionAccessArgs
check-not-pausedprivate
check-ownerprivate
collect-feeprivate
validate-tip-amountprivateamount: uint
track-new-tipperprivate
track-largest-tipprivateamount: uint
get-versionread-only
get-contract-nameread-only
get-total-tipsread-only
get-tip-countread-only
get-total-fees-collectedread-only
get-interaction-feeread-only
get-user-tips-sentread-onlyuser: principal
get-user-tips-receivedread-onlyuser: principal
get-user-tip-countread-onlyuser: principal
get-unique-tippersread-only
get-largest-tipread-only
get-last-activity-blockread-only
is-contract-pausedread-only
get-statsread-only
get-contract-inforead-only
tippublicamount: uint
tip-userpublicrecipient: principal, amount: uint
quick-tippublic
pingpublic
pausepublic
unpausepublic