Source Code

;; Author: Eriq Ferrari  
;; Name: eriq.btc  
;; Title: STX Raffle
;; Version: 1.0
;; Website: www.stxmap.co
;; License: MIT
;;
;; The STX Raffle is an innovative on-chain raffle system built on the Stacks 
;; blockchain, offering a secure and transparent mechanism for organizing raffles 
;; with unpredictable randomness. This contract supports three types of raffles: 
;; STX, fungible tokens (FT), and non-fungible tokens (NFT). Participants can 
;; enjoy a fair and trustless raffle experience where the creator receives the 
;; collected funds and the winner receives the prize.
;;
;; Key features include the ability to set a maximum ticket limit per wallet, 
;; ensuring fair participation. For FT and NFT raffles, tickets can be offered 
;; for free, with users only covering the transaction fees. The randomness is 
;; derived from a combination of the block Header ID Hash, the timestamp, and 
;; the raffle's name hash, creating a robust source of entropy.
;;
;; The draw function works by first generating a keccak256 hash of the raffle's 
;; name. This name hash is then combined with the timestamp and hashed again 
;; using keccak256. The resulting hash is further combined with the block Header 
;; ID Hash and hashed once more using keccak256, like this:
;; keccak256(id header + keccak256(timestamp + keccak256(name))). This process 
;; produces a final hash that is used to determine the winner, ensuring a high 
;; level of unpredictability and security in the raffle outcome.
;;
;; The draw function is a read-only function, allowing anyone to verify the 
;; results of the raffle at any time without incurring any costs. This function 
;; is crucial for maintaining transparency and fairness, as participants can 
;; independently confirm the outcome of the raffle without relying on third parties. 
;; The read-only nature ensures that the function is accessible and cost-free, 
;; encouraging broader participation in the verification process.
;;
;; Additionally, the function responsible for sending the prize is public, meaning 
;; that any participant or observer can trigger it. This design choice reinforces 
;; the decentralized nature of the contract, ensuring that the prize distribution 
;; is open and transparent. By allowing anyone to trigger the prize distribution, 
;; the contract minimizes the risk of centralized control or manipulation.
;;
;; This on-chain randomness is both transparent and verifiable, providing all 
;; participants with confidence in the fairness of the raffle process. The raffle 
;; operates without a predefined time limit, allowing the draw to be triggered 
;; only after all tickets are sold. This ensures that the raffle progresses 
;; fairly and that the draw can only occur once the participation criteria are 
;; fully met. This feature provides participants with certainty that the raffle 
;; will only conclude when all tickets are accounted for, further enhancing the 
;; overall transparency and fairness of the system.
;;
;; In terms of functionality, the contract is designed to be user-friendly and 
;; accessible, with clear parameters for entering and managing raffles. Users 
;; can easily participate in raffles using STX to purchase tickets, with 
;; the entire process being managed on-chain to ensure full transparency. The 
;; use of blockchain technology guarantees that every step of the raffle, from 
;; ticket purchase to winner selection, is recorded and cannot be altered or 
;; tampered with.
;;
;; This contract leverages the power of Stacks to deliver a fully decentralized, 
;; transparent, and secure raffle experience. Users can rest assured that the 
;; randomness and the process as a whole are immune to manipulation, providing 
;; a trustworthy and enjoyable platform for decentralized raffles. Furthermore, 
;; the combination of multiple sources of randomness, along with the public draw 
;; function, establishes a robust system where fairness is at the forefront of 
;; every raffle.
;;
;; The STX Raffle represents a new era in decentralized gaming and fundraising, 
;; offering users a reliable and transparent way to engage in raffles. Whether 
;; for entertainment, charity, or other purposes, this contract ensures that 
;; every participant has a fair chance to win, with the entire process being 
;; open to scrutiny and verification by anyone with access to the blockchain.
;;
;;;;;;;;;;;;;;;;;;;;;;;; Copyright 2024 Enrico Ferrari ;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Permission is hereby granted, free of charge, to any person obtaining a copy 
;; of this software and associated documentation files (the Software), to deal 
;; in the Software without restriction, including without limitation the rights 
;; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
;; copies of the Software, and to permit persons to whom the Software is furnished 
;; to do so, subject to the following conditions:
;;
;; The above copyright notice and this permission notice shall be included in 
;; all copies or substantial portions of the Software.
;;
;; THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
;; IN THE SOFTWARE.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;HERE START THE CODE;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Mainnet nft trait implementation
(use-trait nft-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

;; sip-09 function implementation
(define-private (transfer-nft (token-contract <nft-trait>) (token-id uint) (sender principal) (recipient principal))
    (contract-call? token-contract transfer token-id sender recipient)
)

;; Mainnet ft trait implementation
(use-trait sip-010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

;; sip-010 function implementation
(define-private (transfer-ft (token-contract <sip-010-trait>) (quantity uint) (sender principal) (recipient principal))
    (contract-call? token-contract transfer quantity sender recipient none)
)

;; Errors

(define-constant NO-RAFFLE u100)
(define-constant RAFFLE-EXIST u101)
(define-constant EMPTY-VALUE u102)
(define-constant NOT-ZERO u103)
(define-constant NOT-ENOUGH-TICKETS u104)
(define-constant LIMIT-EXCEED u105)
(define-constant ACTIVE-EXCEED u106)
(define-constant HASH-ERROR u200)
(define-constant DATA-ERROR u201)
(define-constant ENDED u300)
(define-constant ACTIVE u301)
(define-constant DRAFTED u302)
(define-constant WRONG-TYPE u303)
(define-constant WRONG-ASSET u304)
(define-constant NOT-THE-OWNER u400)
(define-constant OUT-OF-RANGE u401)

;; Defining main maps for raffles

(define-map raffles
  uint
  { 
    creator: principal,
    name: (string-ascii 32),
    ticket-price: uint,
    max-tickets: uint,
    wallet-limit: uint,
    next-ticket: uint,
    prize-type: (string-ascii 4),
    prize-contract: principal,
    prize-id: uint,
    prize-quantity: uint,
    protocol-fee: uint,
    end-block: uint,
    status: (string-ascii 8)
  }
)

(define-map raffle-tickets
  {raffle: uint, ticket: uint}
  {buyer: principal, block: uint, winner: bool}
)

(define-map raffle-tickets-per-wallet
  {buyer: principal, raffle: uint}
  uint
)

(define-map raffle-winners
  uint
  {winner: principal, ticket: uint}
)

;; Contract variables

(define-data-var owner principal tx-sender)
(define-data-var protocol principal 'SPXYTPQSRP2J3H76Z1V0P5S55VX2ZC3Q58JXANTJ)
(define-data-var protocol-fee uint u250)
(define-data-var creation-cost uint u1000000)
(define-data-var raffle-count uint u1)
(define-data-var active-raffle (list 64000 uint) (list))
(define-data-var ready-to-draw-raffle (list 64000 uint) (list))

;; Filters

(define-private (is-not-zero (num uint))
(> num u0)
)

;; Functions to manage the contract variable

(define-public (set-new-owner (new-owner principal))
(begin 
(asserts! (is-eq tx-sender (var-get owner)) (err NOT-THE-OWNER))
(var-set owner new-owner)
(ok true)
))

(define-public (set-new-protocol (new-protocol principal))
(begin
(asserts! (is-eq tx-sender (var-get owner)) (err NOT-THE-OWNER))
(var-set protocol new-protocol)
(ok true)
))

(define-public (set-new-protocol-fee (new-fee uint))
(begin 
(asserts! (is-eq tx-sender (var-get owner)) (err NOT-THE-OWNER))
(asserts! (and (>= u100 new-fee) (<= u500 new-fee)) (err OUT-OF-RANGE))
(var-set protocol-fee new-fee)
(ok true)
))

(define-public (set-new-cost (new-cost uint))
(begin 
(asserts! (is-eq tx-sender (var-get owner)) (err NOT-THE-OWNER))
(asserts! (> new-cost u0) (err NOT-ZERO))
(var-set creation-cost new-cost)
(ok true)
))

;; stx-raffle

(define-public (create-raffle-stx (name (string-ascii 32)) (price uint) (max uint) (wallet-limit uint))
  (let (
        (current-raffle-count (var-get raffle-count))
        (prize-contract (as-contract tx-sender))
        (active (var-get active-raffle))
    )
    (begin
      ;; Ensure the raffle ID does not already exist and parameters are correct
      (asserts! (is-none (map-get? raffles current-raffle-count)) (err RAFFLE-EXIST))
      (asserts! (> (len name) u0) (err EMPTY-VALUE))
      (asserts! (> price u0) (err NOT-ZERO))
      (asserts! (>= max u10) (err NOT-ENOUGH-TICKETS))
      (asserts! (< wallet-limit max) (err LIMIT-EXCEED))
      (asserts! (< (len active) u64000) (err ACTIVE-EXCEED) )
      ;; Add the map to active raffles
      (var-set active-raffle (unwrap-panic (as-max-len? (append active current-raffle-count) u64000)))
      ;; Insert the new raffle with initial values
      (map-set raffles current-raffle-count {
        creator: tx-sender,
        name: name,
        ticket-price: price,
        max-tickets: max,
        wallet-limit: wallet-limit,
        next-ticket: u1,
        prize-type: "stx",
        prize-contract: (as-contract tx-sender),
        prize-id: u1,
        prize-quantity: (* price max),
        protocol-fee: (var-get protocol-fee),
        end-block: u0,
        status: "created"
      })

      ;; Increment the raffle count
      (var-set raffle-count (+ current-raffle-count u1))
      (print {a: "create", raffle: current-raffle-count, type: "stx", price: price, max: max})
      (try! (stx-transfer? (var-get creation-cost) tx-sender (var-get protocol)))
    )
    (ok true)
  )
)

;; nft-raffle

(define-public (create-raffle-nft (name (string-ascii 32)) (price uint) (max uint) (nft-contract <nft-trait>) (token-id uint) (wallet-limit uint))
  (let (
        (current-raffle-count (var-get raffle-count))
        (active (var-get active-raffle))
    )
    (begin
      ;; Ensure the raffle ID does not already exist
      (asserts! (is-none (map-get? raffles current-raffle-count)) (err RAFFLE-EXIST))
      (asserts! (> (len name) u0) (err EMPTY-VALUE))
      (asserts! (> max u10) (err NOT-ENOUGH-TICKETS))
      (asserts! (< wallet-limit max) (err LIMIT-EXCEED))
      (asserts! (< (len active) u64000) (err ACTIVE-EXCEED) )
      ;; Add the map to active raffles
      (var-set active-raffle (unwrap-panic (as-max-len? (append active current-raffle-count) u64000)))
      ;; Insert the new raffle with initial values
      (map-set raffles current-raffle-count {
        creator: tx-sender,
        name: name,
        ticket-price: price,
        max-tickets: max,
        wallet-limit: wallet-limit,
        next-ticket: u1,
        prize-type: "nft",
        prize-contract: (contract-of nft-contract),
        prize-id: token-id,
        prize-quantity: u1,
        protocol-fee: (var-get protocol-fee),
        end-block: u0,
        status: "created"
      })

      ;; Increment the raffle count
      (var-set raffle-count (+ current-raffle-count u1))
      (print {a: "create", raffle: current-raffle-count, type: "nft", price: price, max-tickets: max, prize-contract: nft-contract, prize-id: token-id})
      ;; if ticket is free send a creation fee
      (if (> price u0)
          (try! (transfer-nft nft-contract token-id tx-sender (as-contract tx-sender)))
          (begin 
          (try! (transfer-nft nft-contract token-id tx-sender (as-contract tx-sender)))
          (try! (stx-transfer? (var-get creation-cost) tx-sender (var-get protocol)))
          )
      )
      
      (ok name)
    )
  )
)

;; ft-raffle

(define-public (create-raffle-ft (name (string-ascii 32)) (price uint) (max uint) (ft-contract <sip-010-trait>) (quantity uint) (wallet-limit uint))
  (let (
        (current-raffle-count (var-get raffle-count))
        (active (var-get active-raffle))
    )
    (begin
      ;; Ensure the raffle ID does not already exist
      (asserts! (is-none (map-get? raffles current-raffle-count)) (err RAFFLE-EXIST))
      (asserts! (> (len name) u0) (err EMPTY-VALUE))
      (asserts! (> max u10) (err NOT-ENOUGH-TICKETS))
      (asserts! (< wallet-limit max) (err LIMIT-EXCEED))
      (asserts! (< (len active) u64000) (err ACTIVE-EXCEED) )
      ;; Add the map to active raffles
      (var-set active-raffle (unwrap-panic (as-max-len? (append active current-raffle-count) u64000)))
      ;; Insert the new raffle with initial values
      (map-set raffles current-raffle-count {
        creator: tx-sender,
        name: name,
        ticket-price: price,
        max-tickets: max,
        wallet-limit: wallet-limit,
        next-ticket: u1,
        prize-type: "ft",
        prize-contract: (contract-of ft-contract),
        prize-id: u1,
        prize-quantity: quantity,
        protocol-fee: (var-get protocol-fee),
        end-block: u0,
        status: "created"
      })

      ;; Increment the raffle count
      (var-set raffle-count (+ current-raffle-count u1))
      (print {a: "create", raffle: current-raffle-count, type: "ft", price: price, max-tickets: max, token: ft-contract, quantity: quantity})
      ;; if ticket is free send a creation fee
      (if (> price u0)
          (try! (transfer-ft ft-contract quantity tx-sender (as-contract tx-sender) ))
          (begin 
          (try! (transfer-ft ft-contract quantity tx-sender (as-contract tx-sender) ))
          (try! (stx-transfer? (var-get creation-cost) tx-sender (var-get protocol)))
          )
      )
      
      (ok name)
    )
  )
)

;; buy-ticket

(define-public (buy-ticket (raffle-id uint))
  (let (
        ;; Fetch raffle details
        (raffle (map-get? raffles raffle-id))
    )
    ;; Ensure the raffle exists
    (asserts! (is-some raffle) (err NO-RAFFLE))

    (let (
          (raffle-data (unwrap! raffle (err DATA-ERROR)))
          (current-ticket (get next-ticket raffle-data))
          (max-ticket (get max-tickets raffle-data))
          (ticket-holder tx-sender)
          (type (get prize-type raffle-data))
          (price (get ticket-price raffle-data))
          (fee (get protocol-fee raffle-data))
          (wallet-limit (get wallet-limit raffle-data))
          (wallet-count (get-ticket-per-wallet raffle-id tx-sender))
          
    )
    
    ;; Ensure we haven't exceeded the max number of tickets
    (asserts! (<= current-ticket max-ticket) (err ENDED))
    (asserts! (and (< wallet-count wallet-limit) (> wallet-limit u0)) (err LIMIT-EXCEED))
    
    ;; Add the new ticket to the raffle-tickets map
    (map-set raffle-tickets {raffle: raffle-id, ticket: current-ticket} {buyer: ticket-holder, block: block-height, winner: false})

    ;; Add the new ticket to wallet count
    (map-set raffle-tickets-per-wallet {buyer: ticket-holder,raffle: raffle-id} (+ wallet-count u1))
    (print {a: "buy", raffle: raffle-id, ticket: current-ticket})
    ;; Check if max tickets reached
    (if (<= (+ current-ticket u1) max-ticket)
      (begin 
      ;; Update the raffle with the new ticket number
      (map-set raffles raffle-id
      {
        creator: (get creator raffle-data),
        name: (get name raffle-data),
        ticket-price: price,
        max-tickets: max-ticket,
        wallet-limit: (get wallet-limit raffle-data),
        next-ticket: (+ current-ticket u1),
        prize-type: type,
        prize-contract: (get prize-contract raffle-data),
        prize-id: (get prize-id raffle-data),
        prize-quantity: (get prize-quantity raffle-data),
        protocol-fee: fee,
        end-block: u0,
        status: "open"
      }
      )
      (pay-ticket price)
      )
      (let (
        (active (var-get active-raffle))
        (replaced (replace-at? active (unwrap-panic (index-of? active raffle-id)) u0) )
        (filtered (filter is-not-zero (unwrap-panic replaced)))
        (ready (var-get ready-to-draw-raffle))
        (added (unwrap-panic (as-max-len? (append ready raffle-id) u64000)))
      )
      ;; Remove the raffle from active list
      (var-set active-raffle filtered)
      ;; Add the raffle to ready to draw list
      (var-set ready-to-draw-raffle added)
      ;; Finalize the raffle with the last ticket number
      (map-set raffles raffle-id
      {
        creator: (get creator raffle-data),
        name: (get name raffle-data),
        ticket-price: price,
        max-tickets: max-ticket,
        wallet-limit: (get wallet-limit raffle-data),
        next-ticket: (+ current-ticket u1),
        prize-type: type,
        prize-contract: (get prize-contract raffle-data),
        prize-id: (get prize-id raffle-data),
        prize-quantity: (get prize-quantity raffle-data),
        protocol-fee: fee,
        end-block: block-height,
        status: "closed"
      }
      )

      (print {a: "closed", raffle: raffle-id, block: block-height})
      (pay-ticket price)
      )
      
    )))
)

;; pay-ticket if not free

(define-private (pay-ticket (price uint))
  (if (> price u0)
        (stx-transfer? price tx-sender (as-contract tx-sender))
        (ok true)
      )
)

;; check-err to loop in bulk functions

(define-private (check-err (result (response bool uint)) (prior (response bool uint)))
  (match prior ok-value result err-value (err err-value))
)

;; buy many tickets

(define-public (buy-many-tickets (raffles-list (list 100 uint)))
  (fold check-err (map buy-ticket raffles-list) (ok true))
)

;; verify many raffle

(define-read-only (draw-many (raffles-to-draw (list 100 uint)))
(map draw raffles-to-draw)
)

;; draw and verify the results

(define-read-only (draw (raffle-id uint))
 
    (let (
      ;; First we get the raffle data
      (raffle (unwrap! (map-get? raffles raffle-id) (err NO-RAFFLE)))
      (block-time (unwrap! (get-block-info? time (get end-block raffle)) (err ACTIVE)))
      (block-id (unwrap! (get-block-info? id-header-hash (get end-block raffle)) (err DATA-ERROR)))
      ;; Hash conbination of raffle name, block-time and id header hash
      (name-hash (keccak256 (unwrap-panic (to-consensus-buff? (get name raffle)))))
      (time-hash (keccak256 (concat (unwrap-panic (to-consensus-buff? block-time)) name-hash)))
      (full-hash (keccak256 (concat block-id time-hash)))
      (hash-slice (as-max-len? (unwrap! (slice? full-hash u16 u32) (err HASH-ERROR)) u16) )
      (hash-uint (buff-to-uint-le (unwrap! hash-slice (err HASH-ERROR))))
      (uint-str (int-to-ascii hash-uint))
      (uint-len (len uint-str))
      (ticket-len (len (int-to-ascii (get max-tickets raffle))))
      (start  (- uint-len ticket-len))
        (sel (slice? uint-str start uint-len))
        (sel-two (slice? uint-str (+ u1 start) uint-len))
        (sel-some (string-to-uint? (unwrap! sel (err DATA-ERROR))))
        (sel-two-some (string-to-uint? (unwrap! sel-two (err DATA-ERROR))))
        (selected (unwrap! sel-some (err DATA-ERROR)))
        (selected-two (unwrap! sel-two-some (err DATA-ERROR)))
    )
    (asserts! (> (get end-block raffle) u0) (err ACTIVE))
    (if (is-eq selected u0)
        (ok (get max-tickets raffle))
        (if (> selected (get max-tickets raffle))
        (if (is-eq selected-two u0)
        (ok u1)
        (ok selected-two)
        )
        (ok selected)
    )
    )
    )
)

;; Send the prize for STX Raffle

(define-public (send-prize-stx (raffle-id uint)) 
  (let (
    ;; Fetch raffle details
    (raffle (unwrap! (map-get? raffles raffle-id) (err NO-RAFFLE) ))
    (type (get prize-type raffle))
    (status (get status raffle))
    (end-block (get end-block raffle))
    (max-tickets (get max-tickets raffle))
    (price (get ticket-price raffle))
    (total-amount (* max-tickets price))
    )
    (asserts! (is-eq type "stx")  (err WRONG-TYPE))
    (asserts! (> end-block u0)  (err ACTIVE))
    (asserts! (is-eq status "closed") (err DRAFTED))
    (let (
      (winner (unwrap! (draw raffle-id) (err DATA-ERROR) ))
      (winner-address (unwrap-panic (get-ticket-owner raffle-id winner )))
      (ready (var-get ready-to-draw-raffle))
      (replaced (replace-at? ready (unwrap-panic (index-of? ready raffle-id)) u0) )
      (filtered (filter is-not-zero (unwrap-panic replaced)))
    )
    ;; Remove the raffle from ready to draw list
    (var-set ready-to-draw-raffle filtered)
    (map-set raffle-winners raffle-id {winner: winner-address, ticket: winner})
    (map-set raffle-tickets {raffle: raffle-id, ticket: winner} {buyer: winner-address, block: (unwrap-panic (get-ticket-block raffle-id winner)), winner: true})
    (as-contract (stx-transfer? total-amount tx-sender winner-address ))
    )
  )
)

;; Send the prize for NFT Raffle

(define-public (send-prize-nft (raffle-id uint) (nft-contract <nft-trait>)) 
  (let (
    ;; Fetch raffle details
    (raffle (unwrap! (map-get? raffles raffle-id) (err NO-RAFFLE) ))
    (type (get prize-type raffle))
    (status (get status raffle))
    (end-block (get end-block raffle))
    (max-tickets (get max-tickets raffle))
    (prize-contract (get prize-contract raffle))
    (price (get ticket-price raffle))
    (token-id (get prize-id raffle))
    )
    (asserts! (is-eq prize-contract (contract-of nft-contract)) (err WRONG-ASSET))
    (asserts! (is-eq type "nft")  (err WRONG-TYPE))
    (asserts! (> end-block u0)  (err ACTIVE))
    (asserts! (is-eq status "closed") (err DRAFTED))
    (let (
      (winner (unwrap! (draw raffle-id) (err DATA-ERROR) ))
      (winner-address (unwrap-panic (get-ticket-owner raffle-id winner )))
      (ready (var-get ready-to-draw-raffle))
      (replaced (replace-at? ready (unwrap-panic (index-of? ready raffle-id)) u0) )
      (filtered (filter is-not-zero (unwrap-panic replaced)))
    )
    ;; Remove the raffle from ready to draw list
    (var-set ready-to-draw-raffle filtered)
    (map-set raffle-winners raffle-id {winner: winner-address, ticket: winner})
    (map-set raffle-tickets {raffle: raffle-id, ticket: winner} {buyer: winner-address, block: (unwrap-panic (get-ticket-block raffle-id winner)), winner: true})
    (print {a: "draw-nft", winner: winner, prize-collection: nft-contract, prize-id: token-id})
    (if (> price u0)
    ;; if the raffle is not free send fee to protocol and remainder to creator 
    (let (
      (total-amount (* max-tickets price))
      (fee-amount (/ (* total-amount (get protocol-fee raffle)) u10000))
      (prize (- total-amount fee-amount))
    )
  (try! (as-contract (stx-transfer? fee-amount tx-sender (var-get protocol))))
  (try! (as-contract (stx-transfer? prize  tx-sender (get creator raffle))))
  (as-contract (transfer-nft nft-contract token-id  tx-sender winner-address))
  )
  (as-contract (transfer-nft nft-contract token-id tx-sender winner-address))
  )
  )
  )
)

;; Send the prize for FT Raffle

(define-public (send-prize-ft (raffle-id uint) (ft-contract <sip-010-trait>)) 
  (let (
    ;; Fetch raffle details
    (raffle (unwrap! (map-get? raffles raffle-id) (err NO-RAFFLE) ))
    (type (get prize-type raffle))
    (status (get status raffle))
    (end-block (get end-block raffle))
    (max-tickets (get max-tickets raffle))
    (prize-contract (get prize-contract raffle))
    (price (get ticket-price raffle))
    (quantity (get prize-quantity raffle))
    )
    (asserts! (is-eq prize-contract (contract-of ft-contract)) (err WRONG-ASSET))
    (asserts! (is-eq type "ft")  (err WRONG-TYPE))
    (asserts! (> end-block u0)  (err ACTIVE))
    (asserts! (is-eq status "closed") (err DRAFTED))
    (let (
      (winner (unwrap! (draw raffle-id) (err DATA-ERROR) ))
      (winner-address (unwrap-panic (get-ticket-owner raffle-id winner )))
      (ready (var-get ready-to-draw-raffle))
      (replaced (replace-at? ready (unwrap-panic (index-of? ready raffle-id)) u0) )
      (filtered (filter is-not-zero (unwrap-panic replaced)))
    )
    ;; Remove the raffle from ready to draw list
    (var-set ready-to-draw-raffle filtered)
    (map-set raffle-winners raffle-id {winner: winner-address, ticket: winner})
    (map-set raffle-tickets {raffle: raffle-id, ticket: winner} {buyer: winner-address, block: (unwrap-panic (get-ticket-block raffle-id winner)), winner: true})
    (print {a: "draw-ft", winner: winner, tokens: ft-contract, quantity: quantity})
    (if (> price u0)
    ;; if the raffle is not free send fee to protocol and remainder to creator 
    (let (
      (total-amount (* max-tickets price))
      (fee-amount (/ (* total-amount (get protocol-fee raffle)) u10000))
      (prize (- total-amount fee-amount))
    )
  (try! (as-contract (stx-transfer? fee-amount tx-sender (var-get protocol))))
  (try! (as-contract (stx-transfer? prize  tx-sender (get creator raffle))))
  (as-contract (transfer-ft ft-contract quantity tx-sender winner-address))
  )
  (as-contract (transfer-ft ft-contract quantity (as-contract tx-sender) winner-address))
  )
  )
  )
)

;; Read-only functions

(define-read-only (get-ticket-owner (raffle-id uint) (ticket uint ))
 (let (
  (raffle-ticket (map-get? raffle-tickets {raffle: raffle-id, ticket: ticket}))
 )
 (if (is-some raffle-ticket)
 (ok (unwrap-panic (get buyer raffle-ticket)))
 (ok (as-contract tx-sender))
 )
 )
)

(define-read-only (get-ticket-block (raffle-id uint) (ticket uint ))
 (let (
  (raffle-ticket (map-get? raffle-tickets {raffle: raffle-id, ticket: ticket}))
 )
 (if (is-some raffle-ticket)
 (ok (unwrap-panic (get block raffle-ticket)))
 (ok u0)
 )
 )
)

(define-read-only (is-ticket-winner (raffle-id uint) (ticket uint ))
 (let (
  (raffle-ticket (map-get? raffle-tickets {raffle: raffle-id, ticket: ticket}))
 )
 (if (is-some raffle-ticket)
 (ok (unwrap-panic (get winner raffle-ticket)))
 (ok false)
 )
 )
)

(define-read-only (get-raffle-winner (raffle-id uint) )
 (let (
  (raffle-winner (map-get? raffle-winners raffle-id))
 )
 (if (is-some raffle-winner)
 (ok (unwrap-panic (get winner raffle-winner)))
 (ok (as-contract tx-sender))
 )
 )
)

(define-read-only (get-raffle-ticket-winner (raffle-id uint) )
 (let (
  (raffle-winner (map-get? raffle-winners raffle-id))
 )
 (if (is-some raffle-winner)
 (ok (unwrap-panic (get ticket raffle-winner)))
 (ok u0)
 )
 )
)

;; get all the info about a single ticket

(define-read-only (get-ticket-info (raffle-id uint) (ticket uint ))
  (ok {
    buyer: (unwrap-panic (get-ticket-owner raffle-id ticket)),
    block: (unwrap-panic (get-ticket-block raffle-id ticket)),
    winner: (unwrap-panic (is-ticket-winner raffle-id ticket))
  })
)

;; get the full status of a raffle

(define-read-only (get-raffle (raffle-id uint))
  (let (
    (raffle (unwrap! (map-get? raffles raffle-id) (err NO-RAFFLE) ))
  )
  (ok { 
        id: raffle-id,
        creator: (get creator raffle),
        name: (get name raffle),
        ticket-price: (get ticket-price raffle),
        max-tickets: (get max-tickets raffle),
        wallet-limit: (get wallet-limit raffle),
        next-ticket: (get next-ticket raffle),
        prize-type: (get prize-type raffle),
        prize-contract: (get prize-contract raffle),
        prize-id: (get prize-id raffle),
        prize-quantity: (get prize-quantity raffle),
        protocol-fee: (get protocol-fee raffle),
        end-block: (get end-block raffle),
        status: (get status raffle),
        winner: (get-raffle-winner raffle-id),
        ticket-winner: (get-raffle-ticket-winner raffle-id)
      }
    )
  )
)

;; single raffle counter to limit purchase

(define-read-only (get-ticket-per-wallet (raffle-id uint) (user principal))
 (default-to u0 (map-get? raffle-tickets-per-wallet {buyer: user, raffle: raffle-id}))
)

;; lists of active, ready to draw raffles

(define-read-only (get-active-raffle)
(var-get active-raffle)
)

(define-read-only (get-ready-to-draw-raffle)
(var-get ready-to-draw-raffle)
)

;; check how many raffles was created

(define-read-only (get-last-raffle)
  (var-get raffle-count)
)

;; protocol information

(define-read-only (get-owner)
  (var-get owner)
)

(define-read-only (get-protocol-fee)
  (var-get protocol-fee)
)

(define-read-only (get-protocol)
  (var-get protocol)
)

(define-read-only (get-cost)
  (var-get creation-cost)
)

Functions (32)

FunctionAccessArgs
transfer-nftprivatetoken-contract: <nft-trait>, token-id: uint, sender: principal, recipient: principal
transfer-ftprivatetoken-contract: <sip-010-trait>, quantity: uint, sender: principal, recipient: principal
is-not-zeroprivatenum: uint
set-new-ownerpublicnew-owner: principal
set-new-protocolpublicnew-protocol: principal
set-new-protocol-feepublicnew-fee: uint
set-new-costpublicnew-cost: uint
create-raffle-stxpublicname: (string-ascii 32
create-raffle-nftpublicname: (string-ascii 32
create-raffle-ftpublicname: (string-ascii 32
buy-ticketpublicraffle-id: uint
pay-ticketprivateprice: uint
check-errprivateresult: (response bool uint
buy-many-ticketspublicraffles-list: (list 100 uint
draw-manyread-onlyraffles-to-draw: (list 100 uint
drawread-onlyraffle-id: uint
send-prize-stxpublicraffle-id: uint
send-prize-nftpublicraffle-id: uint, nft-contract: <nft-trait>
send-prize-ftpublicraffle-id: uint, ft-contract: <sip-010-trait>
get-ticket-ownerread-onlyraffle-id: uint, ticket: uint
get-ticket-blockread-onlyraffle-id: uint, ticket: uint
is-ticket-winnerread-onlyraffle-id: uint, ticket: uint
get-ticket-inforead-onlyraffle-id: uint, ticket: uint
get-raffleread-onlyraffle-id: uint
get-ticket-per-walletread-onlyraffle-id: uint, user: principal
get-active-raffleread-only
get-ready-to-draw-raffleread-only
get-last-raffleread-only
get-ownerread-only
get-protocol-feeread-only
get-protocolread-only
get-costread-only