;; ember-stake - Staking & Yield Protocol
;; Stake tokens and earn proportional rewards
(define-constant CONTRACT-OWNER tx-sender)
(define-constant REWARD-RATE u100)
(define-constant LOCK-PERIOD u144)
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-ZERO-AMOUNT (err u101))
(define-constant ERR-NO-STAKE (err u102))
(define-constant ERR-STILL-LOCKED (err u103))
(define-constant ERR-INSUFFICIENT-REWARDS (err u104))
(define-map stakers principal
{ amount: uint, start-block: uint, last-claim: uint, rewards-earned: uint })
(define-data-var total-staked uint u0)
(define-data-var reward-pool uint u0)
(define-data-var protocol-fee uint u30)
(define-data-var is-paused bool false)
(define-private (calculate-rewards (staker principal))
(match (map-get? stakers staker)
stake-data
(let ((blocks-elapsed (- block-height (get last-claim stake-data)))
(stake-amount (get amount stake-data)))
(* (* stake-amount REWARD-RATE) blocks-elapsed))
u0))
(define-public (stake (amount uint))
(begin
(asserts! (not (var-get is-paused)) (err u105))
(asserts! (> amount u0) ERR-ZERO-AMOUNT)
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
(match (map-get? stakers tx-sender)
existing (map-set stakers tx-sender
{ amount: (+ (get amount existing) amount),
start-block: block-height,
last-claim: block-height,
rewards-earned: (+ (get rewards-earned existing) (calculate-rewards tx-sender)) })
(map-set stakers tx-sender
{ amount: amount, start-block: block-height, last-claim: block-height, rewards-earned: u0 }))
(var-set total-staked (+ (var-get total-staked) amount))
(ok true)))
(define-public (unstake (amount uint))
(let ((stake-data (unwrap! (map-get? stakers tx-sender) ERR-NO-STAKE)))
(asserts! (>= (get amount stake-data) amount) ERR-INSUFFICIENT-REWARDS)
(asserts! (>= block-height (+ (get start-block stake-data) LOCK-PERIOD)) ERR-STILL-LOCKED)
(try! (as-contract (stx-transfer? amount tx-sender contract-caller)))
(if (is-eq amount (get amount stake-data))
(map-delete stakers tx-sender)
(map-set stakers tx-sender (merge stake-data { amount: (- (get amount stake-data) amount) })))
(var-set total-staked (- (var-get total-staked) amount))
(ok true)))
(define-public (claim-rewards)
(let ((pending (calculate-rewards tx-sender))
(stake-data (unwrap! (map-get? stakers tx-sender) ERR-NO-STAKE)))
(asserts! (> pending u0) ERR-INSUFFICIENT-REWARDS)
(map-set stakers tx-sender (merge stake-data { last-claim: block-height }))
(ok pending)))
(define-public (fund-rewards (amount uint))
(begin
(asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
(ok (var-set reward-pool (+ (var-get reward-pool) amount)))))
(define-read-only (get-stake-info (staker principal)) (ok (map-get? stakers staker)))
(define-read-only (get-total-staked) (ok (var-get total-staked)))
(define-read-only (get-pending-rewards (staker principal)) (ok (calculate-rewards staker)))
(define-read-only (get-protocol-stats)
(ok { total-staked: (var-get total-staked), reward-pool: (var-get reward-pool), rate: REWARD-RATE }))