Source Code

;; StakeFlow Combined Contract v5.1
;; All-in-one: Stake, Rewards, Unstake
;; No cross-contract dependencies - only calls existing NFT and Token contracts

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant TREASURY 'SP1ZYBVXD24AG7HNQ9PXB7TBCY2FD4YWT307FRKA3)
(define-constant REWARD-RATE u1000000) ;; 1 STF (6 decimals) per reward period
(define-constant BLOCKS-PER-REWARD u10) ;; 1 STF every 10 blocks
(define-constant UNSTAKE-FEE u1000) ;; 0.001 STX = 1,000 microSTX

;; Error codes
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-NOT-TOKEN-OWNER (err u101))
(define-constant ERR-ALREADY-STAKED (err u102))
(define-constant ERR-NOT-STAKED (err u103))
(define-constant ERR-TRANSFER-FAILED (err u104))
(define-constant ERR-NO-REWARDS (err u105))
(define-constant ERR-INSUFFICIENT-FUNDS (err u106))

;; Data structures
(define-map staked-nfts
  { token-id: uint }
  {
    owner: principal,
    staked-at: uint,
    last-claim: uint
  }
)

(define-map staker-info
  { staker: principal }
  { staked-count: uint }
)

;; Data vars
(define-data-var total-staked uint u0)
(define-data-var staking-paused bool false)
(define-data-var reward-rate uint REWARD-RATE)
(define-data-var blocks-per-reward uint BLOCKS-PER-REWARD)
(define-data-var rewards-paused bool false)
(define-data-var total-rewards-distributed uint u0)
(define-data-var unstake-fee uint UNSTAKE-FEE)
(define-data-var unstaking-paused bool false)
(define-data-var total-fees-collected uint u0)

;; ==========================================================
;; STAKING FUNCTIONS
;; ==========================================================

(define-public (stake-nft (token-id uint))
  (let
    (
      (nft-owner (unwrap! (contract-call? 'SP5K2RHMSBH4PAP4PGX77MCVNK1ZEED07CWX9TJT.stakeflow-nft-mainnet-v2 get-owner token-id) ERR-NOT-TOKEN-OWNER))
    )
    ;; Check not paused
    (asserts! (not (var-get staking-paused)) ERR-NOT-AUTHORIZED)
    ;; Verify ownership
    (asserts! (is-eq (some tx-sender) nft-owner) ERR-NOT-TOKEN-OWNER)
    ;; Check not already staked
    (asserts! (is-none (map-get? staked-nfts { token-id: token-id })) ERR-ALREADY-STAKED)
    ;; Transfer NFT to this contract
    (try! (contract-call? 'SP5K2RHMSBH4PAP4PGX77MCVNK1ZEED07CWX9TJT.stakeflow-nft-mainnet-v2 transfer token-id tx-sender (as-contract tx-sender)))
    ;; Record stake
    (map-set staked-nfts
      { token-id: token-id }
      {
        owner: tx-sender,
        staked-at: block-height,
        last-claim: block-height
      }
    )
    ;; Update staker info
    (map-set staker-info
      { staker: tx-sender }
      { staked-count: (+ (get-staked-count tx-sender) u1) }
    )
    ;; Update total staked
    (var-set total-staked (+ (var-get total-staked) u1))
    (ok true)
  )
)

;; ==========================================================
;; REWARDS FUNCTIONS
;; ==========================================================

(define-read-only (calculate-rewards (token-id uint))
  (match (map-get? staked-nfts { token-id: token-id })
    stake-data
      (let
        (
          (blocks-staked (- block-height (get last-claim stake-data)))
          (reward-periods (/ blocks-staked (var-get blocks-per-reward)))
          (rewards (* reward-periods (var-get reward-rate)))
        )
        rewards
      )
    u0
  )
)

(define-public (claim-rewards (token-id uint))
  (let
    (
      (stake-data (unwrap! (map-get? staked-nfts { token-id: token-id }) ERR-NOT-STAKED))
      (staker (get owner stake-data))
      (rewards (calculate-rewards token-id))
    )
    ;; Check not paused
    (asserts! (not (var-get rewards-paused)) ERR-NOT-AUTHORIZED)
    ;; Verify caller is the staker
    (asserts! (is-eq tx-sender staker) ERR-NOT-TOKEN-OWNER)
    ;; Check there are rewards to claim
    (asserts! (> rewards u0) ERR-NO-REWARDS)
    ;; Mint rewards to staker
    (try! (contract-call? 'SP5K2RHMSBH4PAP4PGX77MCVNK1ZEED07CWX9TJT.stakeflow-token-mainnet mint-for-staking rewards staker))
    ;; Update last claim
    (map-set staked-nfts
      { token-id: token-id }
      (merge stake-data { last-claim: block-height })
    )
    ;; Update total distributed
    (var-set total-rewards-distributed (+ (var-get total-rewards-distributed) rewards))
    (ok rewards)
  )
)

;; ==========================================================
;; UNSTAKE FUNCTIONS
;; ==========================================================

(define-public (unstake-nft (token-id uint))
  (let
    (
      (stake-data (unwrap! (map-get? staked-nfts { token-id: token-id }) ERR-NOT-STAKED))
      (staker (get owner stake-data))
      (rewards (calculate-rewards token-id))
    )
    ;; Check not paused
    (asserts! (not (var-get unstaking-paused)) ERR-NOT-AUTHORIZED)
    ;; Verify caller is the original staker
    (asserts! (is-eq tx-sender staker) ERR-NOT-TOKEN-OWNER)
    ;; Collect unstake fee
    (try! (stx-transfer? (var-get unstake-fee) tx-sender TREASURY))
    ;; Claim pending rewards if any
    (if (> rewards u0)
      (begin
        (try! (contract-call? 'SP5K2RHMSBH4PAP4PGX77MCVNK1ZEED07CWX9TJT.stakeflow-token-mainnet mint-for-staking rewards staker))
        (var-set total-rewards-distributed (+ (var-get total-rewards-distributed) rewards))
      )
      true
    )
    ;; Transfer NFT back to owner
    (try! (as-contract (contract-call? 'SP5K2RHMSBH4PAP4PGX77MCVNK1ZEED07CWX9TJT.stakeflow-nft-mainnet-v2 transfer token-id tx-sender staker)))
    ;; Remove stake record
    (map-delete staked-nfts { token-id: token-id })
    ;; Update staker info
    (map-set staker-info
      { staker: staker }
      { staked-count: (- (get-staked-count staker) u1) }
    )
    ;; Update total staked
    (var-set total-staked (- (var-get total-staked) u1))
    ;; Update fees collected
    (var-set total-fees-collected (+ (var-get total-fees-collected) (var-get unstake-fee)))
    (ok true)
  )
)

;; Emergency unstake without rewards (if token mint fails)
(define-public (emergency-unstake (token-id uint))
  (let
    (
      (stake-data (unwrap! (map-get? staked-nfts { token-id: token-id }) ERR-NOT-STAKED))
      (staker (get owner stake-data))
    )
    ;; Verify caller is the original staker
    (asserts! (is-eq tx-sender staker) ERR-NOT-TOKEN-OWNER)
    ;; Collect unstake fee
    (try! (stx-transfer? (var-get unstake-fee) tx-sender TREASURY))
    ;; Transfer NFT back to owner
    (try! (as-contract (contract-call? 'SP5K2RHMSBH4PAP4PGX77MCVNK1ZEED07CWX9TJT.stakeflow-nft-mainnet-v2 transfer token-id tx-sender staker)))
    ;; Remove stake record
    (map-delete staked-nfts { token-id: token-id })
    ;; Update staker info
    (map-set staker-info
      { staker: staker }
      { staked-count: (- (get-staked-count staker) u1) }
    )
    ;; Update total staked
    (var-set total-staked (- (var-get total-staked) u1))
    ;; Update fees collected
    (var-set total-fees-collected (+ (var-get total-fees-collected) (var-get unstake-fee)))
    (ok true)
  )
)

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

(define-read-only (get-stake-info (token-id uint))
  (map-get? staked-nfts { token-id: token-id })
)

(define-read-only (is-staked (token-id uint))
  (is-some (map-get? staked-nfts { token-id: token-id }))
)

(define-read-only (get-staked-count (staker principal))
  (default-to u0 (get staked-count (map-get? staker-info { staker: staker })))
)

(define-read-only (get-total-staked)
  (var-get total-staked)
)

(define-read-only (get-blocks-staked (token-id uint))
  (match (map-get? staked-nfts { token-id: token-id })
    stake-data (- block-height (get staked-at stake-data))
    u0
  )
)

(define-read-only (get-staker (token-id uint))
  (match (map-get? staked-nfts { token-id: token-id })
    stake-data (some (get owner stake-data))
    none
  )
)

(define-read-only (is-staking-paused)
  (var-get staking-paused)
)

(define-read-only (get-reward-rate)
  (var-get reward-rate)
)

(define-read-only (get-blocks-per-reward)
  (var-get blocks-per-reward)
)

(define-read-only (is-rewards-paused)
  (var-get rewards-paused)
)

(define-read-only (get-total-rewards-distributed)
  (var-get total-rewards-distributed)
)

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

(define-read-only (get-total-fees-collected)
  (var-get total-fees-collected)
)

(define-read-only (is-unstaking-paused)
  (var-get unstaking-paused)
)

(define-read-only (get-treasury)
  TREASURY
)

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

(define-public (set-staking-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (var-set staking-paused paused))
  )
)

(define-public (set-reward-rate (new-rate uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (var-set reward-rate new-rate))
  )
)

(define-public (set-blocks-per-reward (new-blocks uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (var-set blocks-per-reward new-blocks))
  )
)

(define-public (set-rewards-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (var-set rewards-paused paused))
  )
)

(define-public (set-unstake-fee (new-fee uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (var-set unstake-fee new-fee))
  )
)

(define-public (set-unstaking-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (ok (var-set unstaking-paused paused))
  )
)

Functions (26)

FunctionAccessArgs
stake-nftpublictoken-id: uint
calculate-rewardsread-onlytoken-id: uint
claim-rewardspublictoken-id: uint
unstake-nftpublictoken-id: uint
emergency-unstakepublictoken-id: uint
get-stake-inforead-onlytoken-id: uint
is-stakedread-onlytoken-id: uint
get-staked-countread-onlystaker: principal
get-total-stakedread-only
get-blocks-stakedread-onlytoken-id: uint
get-stakerread-onlytoken-id: uint
is-staking-pausedread-only
get-reward-rateread-only
get-blocks-per-rewardread-only
is-rewards-pausedread-only
get-total-rewards-distributedread-only
get-unstake-feeread-only
get-total-fees-collectedread-only
is-unstaking-pausedread-only
get-treasuryread-only
set-staking-pausedpublicpaused: bool
set-reward-ratepublicnew-rate: uint
set-blocks-per-rewardpublicnew-blocks: uint
set-rewards-pausedpublicpaused: bool
set-unstake-feepublicnew-fee: uint
set-unstaking-pausedpublicpaused: bool