;; StackHub Staking Vault - Gas Optimized
;; Withdrawal fee: 0.5% | Early unstake: 2.5%
;; Uses direct STX transfers - simpler and cheaper
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-OWNER (err u100))
(define-constant ERR-NO-STAKE (err u101))
(define-constant ERR-INSUFFICIENT (err u102))
(define-constant ERR-ZERO (err u103))
(define-constant WITHDRAW-FEE u50) ;; 0.5% = 50/10000
(define-constant EARLY-FEE u250) ;; 2.5% = 250/10000
(define-constant MIN-LOCK-BLOCKS u144) ;; ~1 day in blocks
(define-data-var total-staked uint u0)
(define-data-var total-fees uint u0)
(define-data-var vault-balance uint u0)
(define-map stakes principal {amount: uint, start-block: uint})
;; Read functions
(define-read-only (get-stake (who principal))
(map-get? stakes who))
(define-read-only (get-total-staked)
(var-get total-staked))
(define-read-only (get-total-fees)
(var-get total-fees))
(define-read-only (get-vault-balance)
(var-get vault-balance))
(define-read-only (can-unstake-free (who principal))
(match (map-get? stakes who)
s (>= (- stacks-block-height (get start-block s)) MIN-LOCK-BLOCKS)
false))
;; Stake STX - transfers to contract owner (vault keeper)
(define-public (stake (amount uint))
(let ((current-stake (map-get? stakes tx-sender)))
(asserts! (> amount u0) ERR-ZERO)
(try! (stx-transfer? amount tx-sender CONTRACT-OWNER))
(match current-stake
existing (map-set stakes tx-sender {
amount: (+ (get amount existing) amount),
start-block: stacks-block-height
})
(map-set stakes tx-sender {amount: amount, start-block: stacks-block-height})
)
(var-set total-staked (+ (var-get total-staked) amount))
(var-set vault-balance (+ (var-get vault-balance) amount))
(ok true)))
;; Unstake - owner sends back minus fee
;; Note: This is a 2-step process - user requests, owner fulfills
(define-public (process-unstake (staker principal) (amount uint))
(let (
(stake-data (unwrap! (map-get? stakes staker) ERR-NO-STAKE))
(staked (get amount stake-data))
(is-early (< (- stacks-block-height (get start-block stake-data)) MIN-LOCK-BLOCKS))
(fee-rate (if is-early EARLY-FEE WITHDRAW-FEE))
(fee (/ (* amount fee-rate) u10000))
(payout (- amount fee))
)
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-OWNER)
(asserts! (>= staked amount) ERR-INSUFFICIENT)
(try! (stx-transfer? payout tx-sender staker))
(if (is-eq staked amount)
(map-delete stakes staker)
(map-set stakes staker {
amount: (- staked amount),
start-block: (get start-block stake-data)
})
)
(var-set total-staked (- (var-get total-staked) amount))
(var-set vault-balance (- (var-get vault-balance) amount))
(var-set total-fees (+ (var-get total-fees) fee))
(ok {amount: payout, fee: fee})))
;; Request unstake - emits event for owner to process
(define-public (request-unstake (amount uint))
(let ((stake-data (unwrap! (map-get? stakes tx-sender) ERR-NO-STAKE)))
(asserts! (>= (get amount stake-data) amount) ERR-INSUFFICIENT)
(print {event: "unstake-request", staker: tx-sender, amount: amount})
(ok true)))