Source Code

;; Token Sale Contract V3 - SNTL Token Sale & Distribution
;; Multiple sale mechanisms: ICO, DEX listing, Stake-to-Earn
;; V3: Uses transfer instead of mint (tokens pre-loaded to contract)

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-sale-not-active (err u101))
(define-constant err-sale-ended (err u102))
(define-constant err-insufficient-funds (err u103))
(define-constant err-invalid-amount (err u104))
(define-constant err-max-purchase-exceeded (err u105))
(define-constant err-sale-paused (err u106))

;; Sentinel Token Contract
(define-constant sentinel-token 'SP2PEBKJ2W1ZDDF2QQ6Y4FXKZEDPT9J9R2NKD9WJB.sentinel-token)

;; Sale Configuration
(define-data-var sale-active bool false)
(define-data-var sale-paused bool false)
(define-data-var sale-start-block uint u0)
(define-data-var sale-end-block uint u0)
(define-data-var total-sale-amount uint u50000000000000) ;; 50M SNTL for sale
(define-data-var sold-amount uint u0)
(define-data-var stx-collected uint u0)

;; Sale Tiers (different price tiers)
(define-data-var tier1-price uint u100000) ;; 0.1 STX per 1000 SNTL (early bird)
(define-data-var tier1-amount uint u10000000000000) ;; 10M SNTL
(define-data-var tier1-sold uint u0)

(define-data-var tier2-price uint u120000) ;; 0.12 STX per 1000 SNTL
(define-data-var tier2-amount uint u20000000000000) ;; 20M SNTL
(define-data-var tier2-sold uint u0)

(define-data-var tier3-price uint u150000) ;; 0.15 STX per 1000 SNTL
(define-data-var tier3-amount uint u20000000000000) ;; 20M SNTL
(define-data-var tier3-sold uint u0)

;; Purchase limits
(define-data-var min-purchase-stx uint u1000000) ;; 1 STX minimum
(define-data-var max-purchase-stx uint u100000000) ;; 100 STX maximum per tx
(define-data-var max-purchase-per-address uint u1000000000) ;; 1000 STX max per address

;; Purchase tracking
(define-map purchases principal uint) ;; Total STX spent per address
(define-map tokens-purchased principal uint) ;; SNTL tokens purchased

;; Stake-to-Earn Configuration
(define-data-var stake-reward-pool uint u0) ;; STX collected from sales
(define-data-var stake-reward-rate uint u1000) ;; 0.1% per 1000 blocks
(define-data-var stake-min-amount uint u1000000000) ;; 1000 SNTL minimum stake

;; Staking data
(define-map staked-amount principal uint)
(define-map stake-start-block principal uint)
(define-map total-rewards-earned principal uint)

;; ==========================================
;; PRO MEMBERSHIP TIERS
;; ==========================================
;; Tier thresholds (in SNTL micro-units, 6 decimals)
(define-data-var pro-threshold uint u10000000000) ;; 10,000 SNTL for Pro
(define-data-var vip-threshold uint u50000000000) ;; 50,000 SNTL for VIP
(define-data-var whale-threshold uint u100000000000) ;; 100,000 SNTL for Whale

;; Tier names: 0 = Basic, 1 = Pro, 2 = VIP, 3 = Whale
(define-read-only (get-user-tier (user principal))
  (let
    (
      (staked (default-to u0 (map-get? staked-amount user)))
    )
    (if (>= staked (var-get whale-threshold))
      { tier: u3, name: "whale", staked: staked, required: (var-get whale-threshold) }
      (if (>= staked (var-get vip-threshold))
        { tier: u2, name: "vip", staked: staked, required: (var-get vip-threshold) }
        (if (>= staked (var-get pro-threshold))
          { tier: u1, name: "pro", staked: staked, required: (var-get pro-threshold) }
          { tier: u0, name: "basic", staked: staked, required: (var-get pro-threshold) }
        )
      )
    )
  )
)

;; Check if user has Pro or higher access
(define-read-only (is-pro-member (user principal))
  (let
    (
      (staked (default-to u0 (map-get? staked-amount user)))
    )
    (>= staked (var-get pro-threshold))
  )
)

;; Check if user has VIP or higher access
(define-read-only (is-vip-member (user principal))
  (let
    (
      (staked (default-to u0 (map-get? staked-amount user)))
    )
    (>= staked (var-get vip-threshold))
  )
)

;; Check if user is a Whale
(define-read-only (is-whale-member (user principal))
  (let
    (
      (staked (default-to u0 (map-get? staked-amount user)))
    )
    (>= staked (var-get whale-threshold))
  )
)

;; Get all tier thresholds
(define-read-only (get-tier-thresholds)
  {
    pro: (var-get pro-threshold),
    vip: (var-get vip-threshold),
    whale: (var-get whale-threshold)
  }
)

;; Owner can update tier thresholds
(define-public (set-tier-thresholds (pro uint) (vip uint) (whale uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (< pro vip) (err u700))
    (asserts! (< vip whale) (err u701))
    (var-set pro-threshold pro)
    (var-set vip-threshold vip)
    (var-set whale-threshold whale)
    (ok true)
  )
)

;; ==========================================
;; SALE MANAGEMENT (Owner only)
;; ==========================================

;; Initialize sale
(define-public (initialize-sale (start-block uint) (end-block uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> end-block start-block) (err u200))
    (var-set sale-start-block start-block)
    (var-set sale-end-block end-block)
    (var-set sale-active true)
    (var-set sale-paused false)
    (ok {
      start: start-block,
      end: end-block,
      total-amount: (var-get total-sale-amount)
    })
  )
)

;; Extend sale end block (owner only)
(define-public (extend-sale (new-end-block uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> new-end-block stacks-block-height) (err u800))
    (var-set sale-end-block new-end-block)
    (var-set sale-active true)
    (var-set sale-paused false)
    (ok {
      new-end-block: new-end-block,
      current-block: stacks-block-height
    })
  )
)

;; Reset sale completely (owner only)
(define-public (reset-sale (start-block uint) (end-block uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> end-block start-block) (err u802))
    (var-set sale-start-block start-block)
    (var-set sale-end-block end-block)
    (var-set sale-active true)
    (var-set sale-paused false)
    (ok {
      start: start-block,
      end: end-block
    })
  )
)

;; Pause/Resume sale
(define-public (set-sale-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set sale-paused paused)
    (ok paused)
  )
)

;; Update sale prices
(define-public (set-tier-price (tier uint) (price uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (if (is-eq tier u1)
      (begin
        (var-set tier1-price price)
        (ok true)
      )
      (if (is-eq tier u2)
        (begin
          (var-set tier2-price price)
          (ok true)
        )
        (if (is-eq tier u3)
          (begin
            (var-set tier3-price price)
            (ok true)
          )
          (err u300)
        )
      )
    )
  )
)

;; ==========================================
;; TOKEN PURCHASE
;; ==========================================

;; Calculate which tier and price
(define-read-only (get-current-tier-price)
  (let
    (
      (tier1-remaining (- (var-get tier1-amount) (var-get tier1-sold)))
      (tier2-remaining (- (var-get tier2-amount) (var-get tier2-sold)))
    )
    (if (> tier1-remaining u0)
      (ok { tier: u1, price: (var-get tier1-price), remaining: tier1-remaining })
      (if (> tier2-remaining u0)
        (ok { tier: u2, price: (var-get tier2-price), remaining: tier2-remaining })
        (ok { tier: u3, price: (var-get tier3-price), remaining: (- (var-get tier3-amount) (var-get tier3-sold)) })
      )
    )
  )
)

;; Purchase SNTL tokens with STX
(define-public (purchase-tokens (amount uint))
  (let
    (
      (buyer tx-sender)
      (current-block stacks-block-height)
      (min-required (var-get min-purchase-stx))
      (max-allowed (var-get max-purchase-stx))
    )
    ;; Check sale is active
    (asserts! (var-get sale-active) err-sale-not-active)
    (asserts! (is-eq (var-get sale-paused) false) err-sale-paused)
    (asserts! (>= current-block (var-get sale-start-block)) err-sale-not-active)
    (asserts! (<= current-block (var-get sale-end-block)) err-sale-ended)
    (asserts! (>= amount min-required) err-insufficient-funds)
    (asserts! (<= amount max-allowed) err-max-purchase-exceeded)
    
    ;; Validate user has enough STX
    (asserts! (>= (stx-get-balance buyer) amount) err-insufficient-funds)
    
    ;; Calculate purchase
    (let
      (
        (stx-to-spend amount)
        (tier-info (unwrap! (get-current-tier-price) (err u400)))
        (tier-price (get price tier-info))
        (tier-remaining (get remaining tier-info))
        (tier-num (get tier tier-info))
        (tokens-received (/ (* stx-to-spend u1000000) tier-price))
        (tokens-received-adjusted (/ tokens-received u1000))
        (user-total-spent (default-to u0 (map-get? purchases buyer)))
        (user-new-total (+ user-total-spent stx-to-spend))
      )
      
      ;; Validate purchase
      (asserts! (>= tier-remaining tokens-received-adjusted) (err u500))
      (asserts! (<= user-new-total (var-get max-purchase-per-address)) err-max-purchase-exceeded)
      
      ;; Transfer STX to contract
      (try! (stx-transfer? stx-to-spend buyer (as-contract tx-sender)))
      
      ;; Update sale stats
      (var-set sold-amount (+ (var-get sold-amount) tokens-received-adjusted))
      (var-set stx-collected (+ (var-get stx-collected) stx-to-spend))
      
      ;; Update tier sold amounts
      (if (is-eq tier-num u1)
        (var-set tier1-sold (+ (var-get tier1-sold) tokens-received-adjusted))
        (if (is-eq tier-num u2)
          (var-set tier2-sold (+ (var-get tier2-sold) tokens-received-adjusted))
          (var-set tier3-sold (+ (var-get tier3-sold) tokens-received-adjusted))
        )
      )
      
      ;; Update user purchase tracking
      (map-set purchases buyer user-new-total)
      (map-set tokens-purchased buyer (+ (default-to u0 (map-get? tokens-purchased buyer)) tokens-received-adjusted))
      
      ;; 50% of STX goes to staking rewards
      (let
        (
          (reward-amount (/ stx-to-spend u2))
        )
        (var-set stake-reward-pool (+ (var-get stake-reward-pool) reward-amount))
      )
      
      ;; Transfer SNTL tokens from contract to buyer (tokens pre-loaded)
      (try! (as-contract (contract-call? sentinel-token transfer tokens-received-adjusted tx-sender buyer none)))
      
      (ok {
        purchased: tokens-received-adjusted,
        stx-spent: stx-to-spend,
        tier: tier-num,
        price: tier-price
      })
    )
  )
)

;; ==========================================
;; STAKE-TO-EARN
;; ==========================================

;; Stake SNTL tokens
(define-public (stake-tokens (amount uint))
  (begin
    (asserts! (>= amount (var-get stake-min-amount)) err-invalid-amount)
    
    (let
      (
        (user-balance (unwrap! (contract-call? sentinel-token get-balance tx-sender) (err u600)))
      )
      (asserts! (>= user-balance amount) err-insufficient-funds)
      
      ;; Transfer tokens to contract
      (try! (contract-call? sentinel-token transfer amount tx-sender (as-contract tx-sender) none))
      
      ;; Update staking data
      (map-set staked-amount tx-sender (+ (default-to u0 (map-get? staked-amount tx-sender)) amount))
      (map-set stake-start-block tx-sender stacks-block-height)
      
      (ok {
        staked: amount,
        total-staked: (+ (default-to u0 (map-get? staked-amount tx-sender)) amount)
      })
    )
  )
)

;; Unstake tokens and claim rewards
(define-public (unstake-tokens (amount uint))
  (let
    (
      (staker tx-sender)
      (staked (default-to u0 (map-get? staked-amount staker)))
      (start-block (default-to stacks-block-height (map-get? stake-start-block staker)))
      (blocks-staked (- stacks-block-height start-block))
    )
    (asserts! (>= staked amount) err-insufficient-funds)
    (asserts! (> amount u0) err-invalid-amount)
    
    (let
      (
        (reward-rate (var-get stake-reward-rate))
        (reward-pool (var-get stake-reward-pool))
        (stake-portion (/ (* amount u1000000) staked))
        (blocks-reward (/ (* blocks-staked reward-rate) u1000000))
        (stx-reward (/ (* stake-portion blocks-reward reward-pool) u1000000000000))
      )
      
      ;; Update staking
      (if (is-eq amount staked)
        (begin
          (map-delete staked-amount staker)
          (map-delete stake-start-block staker)
        )
        (begin
          (map-set staked-amount staker (- staked amount))
          (map-set stake-start-block staker stacks-block-height)
        )
      )
      
      ;; Transfer SNTL back
      (try! (as-contract (contract-call? sentinel-token transfer amount (as-contract tx-sender) staker none)))
      
      ;; Transfer STX rewards
      (if (and (> stx-reward u0) (>= reward-pool stx-reward))
        (begin
          (var-set stake-reward-pool (- reward-pool stx-reward))
          (map-set total-rewards-earned staker (+ (default-to u0 (map-get? total-rewards-earned staker)) stx-reward))
          (try! (as-contract (stx-transfer? stx-reward (as-contract tx-sender) staker)))
          (ok {
            unstaked: amount,
            reward: stx-reward,
            total-rewards: (+ (default-to u0 (map-get? total-rewards-earned staker)) stx-reward)
          })
        )
        (ok {
          unstaked: amount,
          reward: u0,
          total-rewards: (default-to u0 (map-get? total-rewards-earned staker))
        })
      )
    )
  )
)

;; Get staking info
(define-read-only (get-stake-info (user principal))
  (let
    (
      (staked (default-to u0 (map-get? staked-amount user)))
      (start-block (default-to stacks-block-height (map-get? stake-start-block user)))
      (blocks-staked (if (> staked u0) (- stacks-block-height start-block) u0))
      (total-rewards (default-to u0 (map-get? total-rewards-earned user)))
    )
    {
      staked: staked,
      start-block: start-block,
      blocks-staked: blocks-staked,
      total-rewards-earned: total-rewards,
      reward-pool: (var-get stake-reward-pool)
    }
  )
)

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

;; Get sale info
(define-read-only (get-sale-info)
  {
    active: (var-get sale-active),
    paused: (var-get sale-paused),
    start-block: (var-get sale-start-block),
    end-block: (var-get sale-end-block),
    total-amount: (var-get total-sale-amount),
    sold: (var-get sold-amount),
    remaining: (- (var-get total-sale-amount) (var-get sold-amount)),
    stx-collected: (var-get stx-collected),
    tier1: {
      price: (var-get tier1-price),
      total: (var-get tier1-amount),
      sold: (var-get tier1-sold),
      remaining: (- (var-get tier1-amount) (var-get tier1-sold))
    },
    tier2: {
      price: (var-get tier2-price),
      total: (var-get tier2-amount),
      sold: (var-get tier2-sold),
      remaining: (- (var-get tier2-amount) (var-get tier2-sold))
    },
    tier3: {
      price: (var-get tier3-price),
      total: (var-get tier3-amount),
      sold: (var-get tier3-sold),
      remaining: (- (var-get tier3-amount) (var-get tier3-sold))
    }
  }
)

;; Get user purchase info
(define-read-only (get-user-purchase (user principal))
  {
    stx-spent: (default-to u0 (map-get? purchases user)),
    tokens-purchased: (default-to u0 (map-get? tokens-purchased user))
  }
)

;; ==========================================
;; ADMIN FUNCTIONS
;; ==========================================

;; Withdraw STX (for liquidity pool)
(define-public (withdraw-stx (amount uint) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (try! (as-contract (stx-transfer? amount (as-contract tx-sender) recipient)))
    (ok true)
  )
)

;; Add STX to reward pool
(define-public (add-reward-stx (amount uint))
  (begin
    (asserts! (>= (stx-get-balance tx-sender) amount) err-insufficient-funds)
    (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
    (var-set stake-reward-pool (+ (var-get stake-reward-pool) amount))
    (ok true)
  )
)

Functions (20)

FunctionAccessArgs
get-user-tierread-onlyuser: principal
is-pro-memberread-onlyuser: principal
is-vip-memberread-onlyuser: principal
is-whale-memberread-onlyuser: principal
get-tier-thresholdsread-only
set-tier-thresholdspublicpro: uint, vip: uint, whale: uint
initialize-salepublicstart-block: uint, end-block: uint
extend-salepublicnew-end-block: uint
reset-salepublicstart-block: uint, end-block: uint
set-sale-pausedpublicpaused: bool
set-tier-pricepublictier: uint, price: uint
get-current-tier-priceread-only
purchase-tokenspublicamount: uint
stake-tokenspublicamount: uint
unstake-tokenspublicamount: uint
get-stake-inforead-onlyuser: principal
get-sale-inforead-only
get-user-purchaseread-onlyuser: principal
withdraw-stxpublicamount: uint, recipient: principal
add-reward-stxpublicamount: uint