Source Code

;; StackFlow Staking Contract
;; Enables FLOW token staking for fee discounts and governance power
;; Tier system: Ripple (1K), Wave (5K), Current (20K), Ocean (100K)

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u200))
(define-constant err-not-found (err u201))
(define-constant err-insufficient-balance (err u202))
(define-constant err-invalid-amount (err u203))
(define-constant err-paused (err u204))
(define-constant err-insufficient-stake (err u205))
(define-constant err-transfer-failed (err u206))

;; Token reference (update after deploying FLOW token)
(define-constant flow-token 'SP3F4WEX90KZQ6D25TWP09J90D6CSYGW1JWXN5YF4.stackflow-flow-token)

;; Tier thresholds (with 6 decimals)
(define-constant ripple-threshold u1000000000) ;; 1,000 FLOW
(define-constant wave-threshold u5000000000) ;; 5,000 FLOW
(define-constant current-threshold u20000000000) ;; 20,000 FLOW
(define-constant ocean-threshold u100000000000) ;; 100,000 FLOW

;; Fee discount percentages (basis points, 100 = 1%)
(define-constant ripple-discount u1000) ;; 10%
(define-constant wave-discount u2500) ;; 25%
(define-constant current-discount u5000) ;; 50%
(define-constant ocean-discount u7500) ;; 75%

;; State
(define-data-var paused bool false)
(define-data-var total-staked uint u0)
(define-data-var total-stakers uint u0)

;; Staking data
(define-map stakes principal {
  amount: uint,
  staked-at: uint,
  last-claim: uint
})

;; Track cumulative rewards (future use)
(define-map cumulative-rewards principal uint)

;; Read-only: Get user's stake
(define-read-only (get-stake (user principal))
  (map-get? stakes user))

;; Read-only: Calculate user's tier
(define-read-only (get-user-tier (user principal))
  (let ((stake-data (get-stake user)))
    (match stake-data
      data
        (let ((amount (get amount data)))
          (if (>= amount ocean-threshold)
            (ok "OCEAN")
            (if (>= amount current-threshold)
              (ok "CURRENT")
              (if (>= amount wave-threshold)
                (ok "WAVE")
                (if (>= amount ripple-threshold)
                  (ok "RIPPLE")
                  (ok "NONE"))))))
      (ok "NONE"))))

;; Read-only: Get fee discount for user (in basis points)
(define-read-only (get-fee-discount (user principal))
  (let ((stake-data (get-stake user)))
    (match stake-data
      data
        (let ((amount (get amount data)))
          (ok (if (>= amount ocean-threshold)
            ocean-discount
            (if (>= amount current-threshold)
              current-discount
              (if (>= amount wave-threshold)
                wave-discount
                (if (>= amount ripple-threshold)
                  ripple-discount
                  u0))))))
      (ok u0))))

;; Read-only: Calculate discounted fee
(define-read-only (calculate-discounted-fee (user principal) (original-fee uint))
  (let ((discount-bps (unwrap-panic (get-fee-discount user))))
    (let ((discount-amount (/ (* original-fee discount-bps) u10000)))
      (ok (- original-fee discount-amount)))))

;; Stake FLOW tokens
(define-public (stake (amount uint))
  (let (
    (sender tx-sender)
    (current-stake (default-to {amount: u0, staked-at: u0, last-claim: u0} (get-stake sender)))
  )
    (asserts! (not (var-get paused)) err-paused)
    (asserts! (> amount u0) err-invalid-amount)
    
    ;; Transfer FLOW from user to this contract
    (match (contract-call? flow-token transfer amount sender (as-contract tx-sender) none)
      success
        (let (
          (new-amount (+ (get amount current-stake) amount))
          (is-new-staker (is-eq (get amount current-stake) u0))
        )
          ;; Update stake
          (map-set stakes sender {
            amount: new-amount,
            staked-at: (if is-new-staker stacks-block-height (get staked-at current-stake)),
            last-claim: stacks-block-height
          })
          
          ;; Update totals
          (var-set total-staked (+ (var-get total-staked) amount))
          (if is-new-staker
            (var-set total-stakers (+ (var-get total-stakers) u1))
            true)
          
          (print {
            event: "stake",
            user: sender,
            amount: amount,
            total-staked: new-amount,
            tier: (unwrap-panic (get-user-tier sender))
          })
          
          (ok true))
      error err-transfer-failed)))

;; Unstake FLOW tokens
(define-public (unstake (amount uint))
  (let (
    (sender tx-sender)
    (current-stake (unwrap! (get-stake sender) err-not-found))
  )
    (asserts! (not (var-get paused)) err-paused)
    (asserts! (> amount u0) err-invalid-amount)
    (asserts! (>= (get amount current-stake) amount) err-insufficient-stake)
    
    ;; Transfer FLOW from contract to user
    (try! (as-contract (contract-call? flow-token transfer amount tx-sender sender none)))
    
    (let ((new-amount (- (get amount current-stake) amount)))
      ;; Update or remove stake
      (if (is-eq new-amount u0)
        (begin
          (map-delete stakes sender)
          (var-set total-stakers (- (var-get total-stakers) u1)))
        (map-set stakes sender (merge current-stake {amount: new-amount})))
      
      ;; Update total
      (var-set total-staked (- (var-get total-staked) amount))
      
      (print {
        event: "unstake",
        user: sender,
        amount: amount,
        remaining: new-amount,
        tier: (unwrap-panic (get-user-tier sender))
      })
      
      (ok true))))

;; Emergency unstake all (no rewards)
(define-public (emergency-unstake)
  (let (
    (sender tx-sender)
    (current-stake (unwrap! (get-stake sender) err-not-found))
    (amount (get amount current-stake))
  )
    ;; Transfer FLOW from contract to user
    (try! (as-contract (contract-call? flow-token transfer amount tx-sender sender none)))
    
    ;; Remove stake
    (map-delete stakes sender)
    
    ;; Update totals
    (var-set total-staked (- (var-get total-staked) amount))
    (var-set total-stakers (- (var-get total-stakers) u1))
    
    (print {
      event: "emergency-unstake",
      user: sender,
      amount: amount
    })
    
    (ok true)))

;; Read-only utilities

(define-read-only (get-stats)
  (ok {
    total-staked: (var-get total-staked),
    total-stakers: (var-get total-stakers),
    paused: (var-get paused)
  }))

(define-read-only (get-tier-thresholds)
  (ok {
    ripple: ripple-threshold,
    wave: wave-threshold,
    current: current-threshold,
    ocean: ocean-threshold
  }))

(define-read-only (get-tier-discounts)
  (ok {
    ripple: ripple-discount,
    wave: wave-discount,
    current: current-discount,
    ocean: ocean-discount
  }))

;; Admin functions

(define-public (pause-staking)
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set paused true)
    (print {event: "staking-paused"})
    (ok true)))

(define-public (unpause-staking)
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set paused false)
    (print {event: "staking-unpaused"})
    (ok true)))

Functions (12)

FunctionAccessArgs
get-stakeread-onlyuser: principal
get-user-tierread-onlyuser: principal
get-fee-discountread-onlyuser: principal
calculate-discounted-feeread-onlyuser: principal, original-fee: uint
stakepublicamount: uint
unstakepublicamount: uint
emergency-unstakepublic
get-statsread-only
get-tier-thresholdsread-only
get-tier-discountsread-only
pause-stakingpublic
unpause-stakingpublic