Source Code

;; contracts/streak-reg-v2.clar

(define-constant BLOCKS_PER_DAY u144)

(define-constant ERR_ALREADY_CHECKED_IN u100)
(define-constant ERR_NOT_CHECKED_IN u101)
(define-constant ERR_ALREADY_SPUN u102)
(define-constant ERR_ALREADY_VOTED u103)

(define-constant SHIELD_PRICE u5000000)

(define-map user-state
  principal
  {
    last-day: uint,
    streak: uint,
    best-streak: uint,
    last-spin-day: uint,
    last-vote-day: uint,
    shields: uint
  }
)

(define-map daily-votes { day: uint, voter: principal } { option: uint })
(define-map streak-sbt uint principal)
(define-data-var next-token-id uint u1)

(define-read-only (get-day-index)
  (/ burn-block-height BLOCKS_PER_DAY)
)

(define-read-only (get-user (user principal))
  (default-to
    {
      last-day: u0,
      streak: u0,
      best-streak: u0,
      last-spin-day: u0,
      last-vote-day: u0,
      shields: u0
    }
    (map-get? user-state user)
  )
)

(define-read-only (streak-level (streak uint))
  (if (>= streak u30) u3 (if (>= streak u14) u2 (if (>= streak u7) u1 u0)))
)

(define-private (mint-or-upgrade-sbt (user principal) (streak uint))
  (let ((level (streak-level streak)))
    (if (is-eq level u0) false 
        (let ((token-id (var-get next-token-id)))
          (begin (map-set streak-sbt token-id user) (var-set next-token-id (+ token-id u1)) true)
        )
    )
  )
)


(define-public (check-in)
  (let (
        (today (get-day-index))
        (user (get-user tx-sender))
        (last-day (get last-day user))
        (current-streak (get streak user))
        (current-shields (get shields user))
       )
    (if (is-eq today last-day)
        (err ERR_ALREADY_CHECKED_IN)

        (let (
              (calc-result 
                (if (is-eq today (+ last-day u1))
                    { s: (+ current-streak u1), sh: current-shields }
                    (if (and (> current-shields u0) (> current-streak u0))
                        { s: (+ current-streak u1), sh: (- current-shields u1) }
                        { s: u1, sh: current-shields }
                    )
                )
              )
              (new-streak (get s calc-result))
              (new-shields (get sh calc-result))
              (best (if (> new-streak (get best-streak user)) new-streak (get best-streak user)))
              
              (reward-amount (+ u10 (* new-streak u2)))
             )
          (begin
            (map-set user-state tx-sender {
              last-day: today,
              streak: new-streak,
              best-streak: best,
              last-spin-day: (get last-spin-day user),
              last-vote-day: (get last-vote-day user),
              shields: new-shields
            })
            
            (mint-or-upgrade-sbt tx-sender new-streak)
            
            (try! (as-contract (contract-call? .streak-token-v2 mint reward-amount tx-sender)))
            
            (ok {
              action: "check-in",
              day: today,
              streak: new-streak,
              shields: new-shields,
              token-reward: reward-amount
            })
          )
        )
    )
  )
)

(define-public (buy-shield)
  (let (
    (user (get-user tx-sender))
  )
    (try! (stx-transfer? SHIELD_PRICE tx-sender (as-contract tx-sender)))
    (map-set user-state tx-sender (merge user { shields: (+ (get shields user) u1) }))
    (ok true)
  )
)

(define-read-only (calculate-reward (streak uint)) (mod (+ burn-block-height streak) u4))

(define-public (spin)
  (let ((today (get-day-index)) (user (get-user tx-sender)))
    (if (not (is-eq today (get last-day user))) (err ERR_NOT_CHECKED_IN)
        (if (is-eq today (get last-spin-day user)) (err ERR_ALREADY_SPUN)
            (let ((tier (calculate-reward (get streak user))))
              (begin
                (map-set user-state tx-sender (merge user { last-spin-day: today }))
                (ok { action: "spin", reward-tier: tier, streak: (get streak user) })
              )
            )
        )
    )
  )
)

(define-public (vote (option uint))
  (let ((today (get-day-index)) (user (get-user tx-sender)))
    (if (is-eq today (get last-vote-day user)) (err ERR_ALREADY_VOTED)
        (begin
          (map-set daily-votes { day: today, voter: tx-sender } { option: option })
          (map-set user-state tx-sender (merge user { last-vote-day: today }))
          (ok { action: "vote", day: today, option: option })
        )
    )
  )
)

Functions (9)

FunctionAccessArgs
get-day-indexread-only
get-userread-onlyuser: principal
streak-levelread-onlystreak: uint
mint-or-upgrade-sbtprivateuser: principal, streak: uint
check-inpublic
buy-shieldpublic
calculate-rewardread-onlystreak: uint
spinpublic
votepublicoption: uint