Source Code

;; --- Traits
(impl-trait 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspot-trait.stackspot-trait)
(use-trait stackspot-trait 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspot-trait.stackspot-trait)

;; Errors
(define-constant ERR_NOT_FOUND (err u1001))
(define-constant ERR_UNAUTHORIZED (err u1101))
(define-constant ERR_ADMIN_ONLY (err u1102))
(define-constant ERR_PARTICIPANT_ONLY (err u1105))
(define-constant ERR_DUPLICATE_PARTICIPANT (err u1104))
(define-constant ERR_INVALID_ADDRESS (err u1201))
(define-constant ERR_INVALID_ARGUMENT_VALUE (err u1202))
(define-constant ERR_INVALID_POT (err u1203))
(define-constant ERR_INSUFFICIENT_BALANCE (err u1301))
(define-constant ERR_INSUFFICIENT_AMOUNT (err u1302))
(define-constant ERR_INSUFFICIENT_POT_BALANCE (err u1303))
(define-constant ERR_INSUFFICIENT_POT_REWARD (err u1304))
(define-constant ERR_POT_JOIN_CLOSED (err u1401))
(define-constant ERR_POT_CLAIM_NOT_REACHED (err u1402))
(define-constant ERR_POOL_ENTRY_PASSED (err u1403))
(define-constant ERR_POT_ALREADY_STARTED (err u1404))
(define-constant ERR_MAX_PARTICIPANTS_REACHED (err u1405))
(define-constant ERR_DELEGATE_FAILED (err u1406))
(define-constant ERR_DISPATCH_FAILED (err u1108))
(define-constant ERR_POT_JOIN_FAILED (err u1408))
(define-constant ERR_TOO_EARLY (err u1409))
(define-constant ERR_INSUFFICIENT_REWARD (err u1410))

(define-constant JOIN_POT_MEMO (unwrap-panic (to-consensus-buff? "join pot")))
(define-constant LEAVE_POT_MEMO (unwrap-panic (to-consensus-buff? "leave pot")))

;; Pot Starter Principal
;; Pot Claimer Principal
(define-data-var pot-starter-principal (optional principal) none)
(define-data-var pot-claimer-principal (optional principal) none)
(define-data-var winners-values (optional {winner-id: uint, winner-address: principal}) none)

;; Pot Participants Maps
(define-map pot-participants-by-principal principal uint)
(define-map pot-participants-by-id uint {participant: principal, amount: uint})

;; Locking Mechanism To Prevent Participants From Trying To Join The Pot While The Pot Is Stacked In Pool
(define-data-var locked bool false)
(define-data-var lock-burn-height uint u0)
(define-data-var first-user-joined (optional uint) none)

;; Get PoX Info and return pool config
(define-constant pox-data (contract-call? 'SP000000000000000000002Q6VF78.pox-4 get-pox-info))
(define-constant pox-details (unwrap! pox-data ERR_NOT_FOUND))
(define-constant MORE_THAN_ONE_CYCLE (+ (get prepare-cycle-length pox-details) (get reward-cycle-length pox-details)) )

;; Get platform fee
(define-constant platform-fee (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots get-fee ))

(define-read-only (get-pool-config)
    (let (

            (first (get first-burnchain-block-height pox-details))
            (cycle-len (get reward-cycle-length pox-details))
            (prepare-len (get prepare-cycle-length pox-details))
            (cycle (/ (- (var-get lock-burn-height) first) cycle-len))
            (cycle-start (+ first (* cycle cycle-len)))
            (next-cycle-start (+ first (* (+ cycle u1) cycle-len)))
        )
        (ok {
            join-end: (- (- next-cycle-start prepare-len) u300),
            prepare-start: (- next-cycle-start prepare-len),
            cycle-end: next-cycle-start,
            reward-release: (+ next-cycle-start u432),
        })
    )
)

;; pot Join Stop validation
(define-read-only (validate-can-pool-pot)
    (let (
            (pool-config (unwrap! (get-pool-config) false))
            (join-end (get join-end pool-config))
        )
        (asserts! (< (var-get lock-burn-height) join-end) false)
        true
    )
)

;; Pot Join Start validation
(define-read-only (validate-can-join-pot)
    (var-get locked)
)

;; Pot Claim Start validation
(define-read-only (validate-can-claim-pot)
    (let (
            (pool-config (unwrap! (get-pool-config) false))
            (reward-release (get reward-release pool-config))
        )
        (asserts! (> burn-block-height reward-release) false)
    )
)

;; This function validates that the reward covers the pot deployment fees`
(define-read-only (validate-pot-value-target-is-met) 
    (>= (var-get total-pot-value) (* pot-min-amount pot-max-participants))
)

(define-read-only (is-locked)
    (var-get locked)
)

(define-read-only (get-pot-details)
    (ok
        {
            pot-participants-count: (var-get last-participant),
            pot-value: (var-get total-pot-value),
            pot-reward-amount: (unwrap! (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token get-balance pot-treasury-address) ERR_NOT_FOUND),
            pot-participant-values: (get-pot-participant-values tx-sender),
            ;; Winner Values
            winners-values: (var-get winners-values),
            ;; Starter Values
            pot-starter-address: (var-get pot-starter-principal),
            ;; Claimer Values
            pot-claimer-address: (var-get pot-claimer-principal),
            pool-config: (unwrap! (get-pool-config) ERR_NOT_FOUND),
            pot-locked: (var-get locked),
            pot-lock-burn-height: (var-get lock-burn-height),
        }
    )
)

;; Total Max Participants
;; Platform Address
;; Pot Treasury Address
(define-constant total-max-participants u100)
(define-constant platform-address (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots get-platform-treasury))

(define-constant pot-treasury-address (as-contract tx-sender))
(define-read-only (get-pot-treasury)
    (ok pot-treasury-address)
)

;; Pot Admin
(define-constant pot-admin tx-sender)
(define-read-only (get-pot-admin)
    (ok pot-admin)
)

;; Last Participant Indexed In The Pot Participants By Id Map
(define-data-var last-participant uint u0)
(define-read-only (get-last-participant)
    (ok (var-get last-participant))
)

(define-read-only (get-configs)
    {
        cycles: pot-cycle,
        min-amount: pot-min-amount,
        max-participants: pot-max-participants,
    }
)

;; Pot Value
(define-data-var total-pot-value uint u0)
(define-read-only (get-pot-value)
    (ok (var-get total-pot-value))
)

;; Increment Pot Value
(define-private (add-pot-value (amount uint))
    (var-set total-pot-value (+ (var-get total-pot-value) amount))
)
;; Decrement Pot Value
(define-private (remove-pot-value (amount uint))
    (var-set total-pot-value (- (var-get total-pot-value) amount))
)

;; Read-Only public function that gets participant by index
(define-read-only (get-by-id-helper (n uint))
    (ok (map-get? pot-participants-by-id n))
)
(define-read-only (get-by-id-helper-private (n uint))
    (map-get? pot-participants-by-id n)
)

(define-read-only (get-pot-participant-values (participant principal))
    (map-get? pot-participants-by-id (default-to u0 (map-get? pot-participants-by-principal participant)))
)

;; Read-Only public function that gets all participants
(define-read-only (get-pot-participants)
    (let (
            (participants-count (var-get last-participant))
            (n (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspot-vrf generate-list u0 participants-count))
            (participants (match n
                value (map get-by-id-helper-private value)
                (list)
            ))
        )
        (ok participants)
    )
)

;; Get Pot ID
(define-read-only (get-pot-id)
    (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots get-token-id pot-treasury-address)
)

;; Get Pot Starter Principal
(define-read-only (get-pot-starter-principal)
    (ok (var-get pot-starter-principal))
)

;; Get Pot Claimer Principal
(define-read-only (get-pot-claimer-principal)
    (ok (var-get pot-claimer-principal))
)

;; Get random digit from VRF and return the winner index
(define-private (get-random-index (participant-count uint))
    (let (
            ;; Get random digit from VRF
            ;; Get sender uint
            (sender-buff (unwrap! (to-consensus-buff? tx-sender) ERR_NOT_FOUND))
            (pot-admin-buff (unwrap! (to-consensus-buff? pot-admin) ERR_NOT_FOUND))
            (merged-buff (concat sender-buff pot-admin-buff))
            (merged-sha256 (sha256 merged-buff))
            (merged-sha256-uint (buff-to-uint-le (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspot-vrf lower-16-le merged-sha256)))
            (vrf-random-digit (unwrap! (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspot-vrf get-random-uint-at-block stacks-block-height) ERR_NOT_FOUND))
        )
        (ok (mod vrf-random-digit participant-count))
    )
)

;; Private helper function that delegates to pot-treasury
(define-private (delegate-to-pot (amount uint) (participant principal))
    (let
        (
            (participants-stx-balance (stx-get-balance participant))
            (index-participants (var-get last-participant))
            (pot-config (get-configs))
            (max-participants (get max-participants pot-config))
            (min-amount (get min-amount pot-config))
        )
        ;; Participants Eligibility Validations
        (asserts! (>= amount min-amount) ERR_INSUFFICIENT_AMOUNT)

        (asserts! (not (is-eq participant pot-treasury-address)) ERR_UNAUTHORIZED)
        (asserts! (not (is-eq participant platform-address)) ERR_UNAUTHORIZED)
        (asserts! (not (is-eq participant pot-admin)) ERR_UNAUTHORIZED)

        (asserts! (<= index-participants max-participants)
            ERR_MAX_PARTICIPANTS_REACHED
        )
        (asserts!
            (is-none (map-get? pot-participants-by-principal participant))
            ERR_DUPLICATE_PARTICIPANT
        )

        ;; Registers Participants Values To The Pot Maps
        (map-insert pot-participants-by-principal participant index-participants)
        (map-insert pot-participants-by-id index-participants {participant: participant, amount: amount})

        ;; Transfers User's Delegated Amount To Pot Treasury
        (asserts! (is-ok (stx-transfer-memo? amount participant pot-treasury-address JOIN_POT_MEMO)) ERR_POT_JOIN_FAILED)

        ;; Updates Pot Value
        (add-pot-value amount)

        ;; Action Log
        (print {
            event: "delegate-to-pot",
            participant: participant,
            amount: amount,
            index: index-participants,
        })

        ;; Updates Last Participant To Next Pot Joiner
        (var-set last-participant (+ index-participants u1))

        ;; Execution Complete
        (ok true)
    )
)

;; Public Function That Initiates The Payments
(define-public (join-pot (amount uint))
    (begin

        ;; Validate can join pot
        ;; Validate amount is greater than 0
        ;; Validate participant is the same as the tx sender
        ;; Delegate to pot
        (asserts! (not (var-get locked)) ERR_POT_JOIN_CLOSED)
        (asserts! (> amount u0) ERR_INSUFFICIENT_AMOUNT)

        (try! (delegate-to-pot amount tx-sender))
        ;; Set first user joined burn height
        (and
            (is-none (var-get first-user-joined))
            (var-set first-user-joined (print (some burn-block-height))))
        (ok true)
    )
)

(define-public (cancel-pot (pot-contract <stackspot-trait>))
    (begin
      (asserts! (not (var-get locked)) ERR_POT_ALREADY_STARTED)
      (asserts! (> burn-block-height (+ (default-to burn-block-height (var-get first-user-joined)) MORE_THAN_ONE_CYCLE)) ERR_TOO_EARLY)
      (asserts! (is-eq (contract-of pot-contract) pot-treasury-address) ERR_ADMIN_ONLY)

      ;; Returns participants principals
      (try! (as-contract (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots dispatch-principals pot-contract)))

      (ok true)
    )
)

;; Public Function That Starts The Jackpot
(define-public (start-stackspot-jackpot (pot-contract <stackspot-trait>))
    (begin
        (var-set lock-burn-height burn-block-height)

        ;; Validate reward covers pot deployment fees
        (asserts! (validate-pot-value-target-is-met) ERR_INSUFFICIENT_REWARD)

        ;; Validate can pool pot
        (asserts! (validate-can-pool-pot) ERR_POOL_ENTRY_PASSED)
        ;; Validate pot treasury is the same as the pot contract
        (asserts! (is-eq pot-treasury-address (contract-of pot-contract)) ERR_UNAUTHORIZED)

        ;; Delegate treasury to pot contract
        (try! (as-contract (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots delegate-treasury pot-contract pot-treasury-address)))

        ;; Set pot starter principal
        (var-set pot-starter-principal (some tx-sender))

        ;; Lock pot
        (var-set locked true)

        ;; Print
        (print {
            event: "start-stackspot-jackpot",
            pot-starter-principal: tx-sender,
            pot-contract: (contract-of pot-contract),
            pot-treasury: pot-treasury-address,
            pot-participants: (unwrap! (get-pot-participants) ERR_NOT_FOUND),
            pot-value: (var-get total-pot-value),
            pot-locked: (var-get locked),
            pot-lock-burn-height: burn-block-height,
        })

        ;; Execution complete
        (ok true)
    )
)

;; Public function that rewards the pot winner, returns participants principals and rewards pot starter and claimer
(define-public (claim-pot-reward (pot-contract <stackspot-trait>))
    (let
        (
            ;; Get pot details
            (pot-details (unwrap! (get-pot-details) (err u999)))
            ;; @value: pot ID
            ;; @value: pot winner's ID
            ;; @value: pot winner's {participant: principal, amount: uint}
            ;; @value: pot winner's principal
            ;; @value: pot winner's reward
            (pot-id (get-pot-id))
            (total-participants (get pot-participants-count pot-details))

            (participants (unwrap! (get-pot-participants) (err u998))) ;; Get participants list
            (stacked-reward (unwrap! (contract-call? 'SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-token get-balance pot-treasury-address) (err u997))) ;; Get stacked reward

            (pot-winner-id (unwrap! (get-random-index total-participants) (err u996)))
            (winner-values (unwrap! (map-get? pot-participants-by-id pot-winner-id) (err u995)))
            (winner (get participant winner-values))
            (winners-reward (* (/ stacked-reward u100) u90)) ;; Calculate winner's reward 99% of stacked reward or 100% of stacked reward

            (pot-starter (get pot-starter-address pot-details))
            (pot-starter-reward (if (> stacked-reward u0) (* (/ stacked-reward u100) u2) u0));; Calculate pot starter's reward

            (claimer tx-sender) ;; Calculate claimer's reward
            (claimer-reward (if (> stacked-reward u0) (* (/ stacked-reward u100) u2) u0))
        )
        ;; Validate can claim pot
        (asserts! (validate-can-claim-pot) ERR_POT_CLAIM_NOT_REACHED)
        (asserts! (> stacked-reward u0) ERR_INSUFFICIENT_POT_REWARD)

        ;; Set pot claimer principal
        (var-set pot-claimer-principal (some tx-sender))

        ;; Set winners address
        (var-set winners-values (some {winner-id: pot-winner-id, winner-address: winner}))

        ;; Returns participants principals
        (try! (as-contract (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots dispatch-principals pot-contract)))

        ;; Disburse rewards
        (try! (as-contract (contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots dispatch-rewards pot-contract)))

        ;; Print
        (print {
            ;; Pot Values
            event: "claim-pot-reward",
            ;; Pot Round Values
            pot-participants-count: total-participants,
            pot-participants: participants,
            pot-value: (var-get total-pot-value),
            pot-yield-amount: stacked-reward,
            ;; Winner Values
            winners-values: (var-get winners-values),
            ;; Starter Values
            starter-address: pot-starter,
            starter-reward-amount: pot-starter-reward,
            ;; Claimer Values
            claimer-address: claimer,
            claimer-reward-amount: claimer-reward,
            ;; Pot Values
            pot-id: pot-id,
            pot-address: pot-treasury-address,
            pot-owner: pot-admin,
            ;; Pot Config Values
            pot-name: pot-name,
            pot-type: pot-type,
            pot-cycle: pot-cycle,
            pot-reward-token: "sbtc",
            pot-min-amount: pot-min-amount,
            pot-max-participants: pot-max-participants,
            ;; Pot Origination Values
            origin-contract-sha-hash: origin-contract-sha-hash,
            stacks-block-height: stacks-block-height,
            burn-block-height: burn-block-height
        })
        ;; Execution complete
        (ok true)
    )
)

(define-read-only (get-pot-cycle) (ok pot-cycle))
(define-read-only (get-pot-min-amount) (ok pot-min-amount))
(define-read-only (get-pot-max-participants) (ok pot-max-participants))
(define-read-only (get-pot-name) (ok pot-name))
(define-read-only (get-pot-type) (ok pot-type))
(define-read-only (get-pot-origin-contract-sha-hash) (ok origin-contract-sha-hash))
(define-read-only (get-pot-reward-token) (ok "sbtc"))

(as-contract (contract-call? 'SPMPMA1V6P430M8C91QS1G9XJ95S59JS1TZFZ4Q4.pox4-multi-pool-v1 allow-contract-caller 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspot-distribute none))
(as-contract (contract-call? 'SP000000000000000000002Q6VF78.pox-4 allow-contract-caller 'SPMPMA1V6P430M8C91QS1G9XJ95S59JS1TZFZ4Q4.pox4-multi-pool-v1 none))

;; Pot Deployer Configuration
(define-constant pot-cycle u1)
(define-constant pot-max-participants u10)
(define-constant pot-min-amount u10)
(define-constant pot-name "stackspot-jackpot-v00")
(define-constant pot-type "Random Jackpot Pot")
(define-constant origin-contract-sha-hash "559bb0511e0b059e7bb458a5482dd59faba3eec0ece45edda1a050851e4a4bc7")
(contract-call? 'SP32XNV169ER2N5NJPJ7REG1RBFA3YJ2XEG0236HX.stackspots register-pot {owner: tx-sender, contract: (as-contract tx-sender), cycles: pot-cycle, type: pot-type, pot-reward-token: "sbtc", min-amount: pot-min-amount, max-participants: pot-max-participants, contract-sha-hash: origin-contract-sha-hash})

Functions (34)

FunctionAccessArgs
get-pool-configread-only
validate-can-pool-potread-only
validate-can-join-potread-only
validate-can-claim-potread-only
validate-pot-value-target-is-metread-only
is-lockedread-only
get-pot-detailsread-only
get-pot-treasuryread-only
get-pot-adminread-only
get-last-participantread-only
get-configsread-only
get-pot-valueread-only
add-pot-valueprivateamount: uint
remove-pot-valueprivateamount: uint
get-by-id-helperread-onlyn: uint
get-by-id-helper-privateread-onlyn: uint
get-pot-participant-valuesread-onlyparticipant: principal
get-pot-participantsread-only
get-pot-idread-only
get-pot-starter-principalread-only
get-pot-claimer-principalread-only
get-random-indexprivateparticipant-count: uint
delegate-to-potprivateamount: uint, participant: principal
join-potpublicamount: uint
cancel-potpublicpot-contract: <stackspot-trait>
start-stackspot-jackpotpublicpot-contract: <stackspot-trait>
claim-pot-rewardpublicpot-contract: <stackspot-trait>
get-pot-cycleread-only
get-pot-min-amountread-only
get-pot-max-participantsread-only
get-pot-nameread-only
get-pot-typeread-only
get-pot-origin-contract-sha-hashread-only
get-pot-reward-tokenread-only