Source Code

(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 ERR_NOT_OWNER u104)
(define-constant ERR_TRANSFER_DISABLED u105)

(define-map user-state
  principal
  {
    last-day: uint,
    streak: uint,
    best-streak: uint,
    last-spin-day: uint,
    last-vote-day: 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
    }
    (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))
       )
    (if (is-eq today (get last-day user))
        (err ERR_ALREADY_CHECKED_IN)

        (let (
              (new-streak
                (if (is-eq today (+ (get last-day user) u1))
                    (+ (get streak user) u1)
                    u1
                )
              )
              (best
                (if (> new-streak (get best-streak user))
                    new-streak
                    (get best-streak user)
                )
              )
             )
          (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)
            })
            
            (mint-or-upgrade-sbt tx-sender new-streak)
            
            (ok {
              action: "check-in",
              day: today,
              streak: new-streak,
              best-streak: best
            })
          )
        )
    )
  )
)

(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 {
                  last-day: (get last-day user),
                  streak: (get streak user),
                  best-streak: (get best-streak user),
                  last-spin-day: today,
                  last-vote-day: (get last-vote-day user)
                })
                (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 {
            last-day: (get last-day user),
            streak: (get streak user),
            best-streak: (get best-streak user),
            last-spin-day: (get last-spin-day user),
            last-vote-day: today
          })
          (ok {
            action: "vote",
            day: today,
            option: option
          })
        )
    )
  )
)


(define-public (transfer-sbt (token-id uint) (to principal))
  (begin
    (asserts! false (err ERR_TRANSFER_DISABLED))
    (ok true)
  )
)

(define-read-only (get-my-state)
  (ok (get-user tx-sender))
)

(define-read-only (get-vote (day uint) (user principal))
  (map-get? daily-votes { day: day, voter: user })
)

(define-read-only (get-sbt-owner (token-id uint))
  (map-get? streak-sbt token-id)
)

Functions (12)

FunctionAccessArgs
get-day-indexread-only
get-userread-onlyuser: principal
streak-levelread-onlystreak: uint
mint-or-upgrade-sbtprivateuser: principal, streak: uint
check-inpublic
calculate-rewardread-onlystreak: uint
spinpublic
votepublicoption: uint
transfer-sbtpublictoken-id: uint, to: principal
get-my-stateread-only
get-voteread-onlyday: uint, user: principal
get-sbt-ownerread-onlytoken-id: uint