Source Code

;; -------------------------------------------------------
;; TimeFi Rewards Contract v-A2
;; Tiered APY based on lock duration
;; -------------------------------------------------------

(define-constant ERR_UNAUTHORIZED (err u200))
(define-constant ERR_NOT_FOUND (err u201))
(define-constant ERR_ALREADY_CLAIMED (err u202))
(define-constant ERR_NOT_MATURED (err u203))
(define-constant ERR_NO_REWARDS (err u204))
(define-constant ERR_INSUFFICIENT_POOL (err u205))

(define-constant DEPLOYER tx-sender)

;; Reward tiers (APY in basis points)
;; Tier 1: 30 days lock = 1% APY (100 BPS)
;; Tier 2: 90 days lock = 3% APY (300 BPS)  
;; Tier 3: 180 days lock = 6% APY (600 BPS)
;; Tier 4: 270 days lock = 9% APY (900 BPS)
;; Tier 5: 365 days lock = 12% APY (1200 BPS)

(define-constant TIER_1_BLOCKS u4320)    ;; ~30 days
(define-constant TIER_2_BLOCKS u12960)   ;; ~90 days
(define-constant TIER_3_BLOCKS u25920)   ;; ~180 days
(define-constant TIER_4_BLOCKS u38880)   ;; ~270 days
(define-constant TIER_5_BLOCKS u52560)   ;; ~365 days

(define-constant TIER_1_APY u100)
(define-constant TIER_2_APY u300)
(define-constant TIER_3_APY u600)
(define-constant TIER_4_APY u900)
(define-constant TIER_5_APY u1200)

(define-data-var rewards-pool uint u0)
(define-data-var total-rewards-paid uint u0)

(define-map claimed-rewards
  { vault-id: uint }
  { amount: uint, claimed-at: uint })

;; -------------------------------------------------------
;; READ: GET TIER FOR LOCK DURATION
;; -------------------------------------------------------

(define-read-only (get-tier (lock-duration uint))
  (if (>= lock-duration TIER_5_BLOCKS)
    { tier: u5, apy: TIER_5_APY }
    (if (>= lock-duration TIER_4_BLOCKS)
      { tier: u4, apy: TIER_4_APY }
      (if (>= lock-duration TIER_3_BLOCKS)
        { tier: u3, apy: TIER_3_APY }
        (if (>= lock-duration TIER_2_BLOCKS)
          { tier: u2, apy: TIER_2_APY }
          (if (>= lock-duration TIER_1_BLOCKS)
            { tier: u1, apy: TIER_1_APY }
            { tier: u0, apy: u0 }))))))

;; -------------------------------------------------------
;; READ: CALCULATE REWARDS FOR VAULT
;; -------------------------------------------------------

(define-read-only (calculate-rewards (vault-id uint))
  (let (
    (vault-result (contract-call? .timefi-vault-v-A2 get-vault vault-id))
  )
    (match vault-result
      vault
        (if (get active vault)
          (let (
            (lock-duration (- (get unlock-time vault) (get lock-time vault)))
            (tier-info (get-tier lock-duration))
            (apy (get apy tier-info))
            (amount (get amount vault))
            ;; Calculate rewards: amount * APY * (duration / year) / 10000
            (yearly-reward (/ (* amount apy) u10000))
            (actual-reward (/ (* yearly-reward lock-duration) u52560))
          )
            (ok { 
              vault-id: vault-id,
              tier: (get tier tier-info),
              apy: apy,
              amount: amount,
              lock-duration: lock-duration,
              reward: actual-reward
            }))
          (ok { vault-id: vault-id, tier: u0, apy: u0, amount: u0, lock-duration: u0, reward: u0 }))
      error ERR_NOT_FOUND)))

;; -------------------------------------------------------
;; READ: GET REWARDS POOL BALANCE
;; -------------------------------------------------------

(define-read-only (get-rewards-pool)
  (ok (var-get rewards-pool)))

;; -------------------------------------------------------
;; READ: GET TOTAL REWARDS PAID
;; -------------------------------------------------------

(define-read-only (get-total-rewards-paid)
  (ok (var-get total-rewards-paid)))

;; -------------------------------------------------------
;; READ: HAS CLAIMED REWARDS
;; -------------------------------------------------------

(define-read-only (has-claimed (vault-id uint))
  (is-some (map-get? claimed-rewards { vault-id: vault-id })))

;; -------------------------------------------------------
;; READ: GET CLAIMED INFO
;; -------------------------------------------------------

(define-read-only (get-claimed-info (vault-id uint))
  (map-get? claimed-rewards { vault-id: vault-id }))

;; -------------------------------------------------------
;; PUBLIC: FUND REWARDS POOL (anyone can fund)
;; Funds go to DEPLOYER who acts as custodian
;; -------------------------------------------------------

(define-public (fund-rewards-pool (amount uint))
  (begin
    (try! (stx-transfer? amount tx-sender DEPLOYER))
    (var-set rewards-pool (+ (var-get rewards-pool) amount))
    (print { event: "pool-funded", funder: tx-sender, amount: amount, new-total: (var-get rewards-pool) })
    (ok true)))

;; -------------------------------------------------------
;; PUBLIC: PROCESS REWARD CLAIM (deployer sends rewards)
;; -------------------------------------------------------

(define-public (process-reward-claim (vault-id uint) (recipient principal) (amount uint))
  (begin
    (asserts! (is-eq tx-sender DEPLOYER) ERR_UNAUTHORIZED)
    (asserts! (<= amount (var-get rewards-pool)) ERR_INSUFFICIENT_POOL)
    (try! (stx-transfer? amount tx-sender recipient))
    (var-set rewards-pool (- (var-get rewards-pool) amount))
    (var-set total-rewards-paid (+ (var-get total-rewards-paid) amount))
    (print { event: "reward-processed", vault-id: vault-id, recipient: recipient, amount: amount })
    (ok true)))

;; -------------------------------------------------------
;; PUBLIC: REQUEST CLAIM REWARDS (user initiates, deployer processes)
;; -------------------------------------------------------

(define-public (request-claim-rewards (vault-id uint))
  (let (
    (vault-data (unwrap! (contract-call? .timefi-vault-v-A2 get-vault vault-id) ERR_NOT_FOUND))
    (rewards-info (unwrap! (calculate-rewards vault-id) ERR_NOT_FOUND))
    (reward-amount (get reward rewards-info))
  )
    ;; Validations
    (asserts! (is-eq (get owner vault-data) tx-sender) ERR_UNAUTHORIZED)
    (asserts! (not (has-claimed vault-id)) ERR_ALREADY_CLAIMED)
    (asserts! (>= tenure-height (get unlock-time vault-data)) ERR_NOT_MATURED)
    (asserts! (> reward-amount u0) ERR_NO_REWARDS)
    (asserts! (>= (var-get rewards-pool) reward-amount) ERR_INSUFFICIENT_POOL)

    ;; Record claim intent (deployer will process)
    (map-set claimed-rewards { vault-id: vault-id }
      { amount: reward-amount, claimed-at: tenure-height })

    (print { 
      event: "rewards-claim-requested",
      vault-id: vault-id,
      claimer: tx-sender,
      amount: reward-amount,
      tier: (get tier rewards-info)
    })
    (ok reward-amount)))

;; -------------------------------------------------------
;; PUBLIC: WITHDRAW EXCESS FROM POOL (admin only)
;; Deployer transfers from their own wallet
;; -------------------------------------------------------

(define-public (withdraw-excess (amount uint) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender DEPLOYER) ERR_UNAUTHORIZED)
    (asserts! (<= amount (var-get rewards-pool)) ERR_INSUFFICIENT_POOL)
    
    (var-set rewards-pool (- (var-get rewards-pool) amount))
    (try! (stx-transfer? amount tx-sender recipient))
    
    (print { event: "excess-withdrawn", amount: amount, recipient: recipient })
    (ok true)))

Functions (10)

FunctionAccessArgs
get-tierread-onlylock-duration: uint
calculate-rewardsread-onlyvault-id: uint
get-rewards-poolread-only
get-total-rewards-paidread-only
has-claimedread-onlyvault-id: uint
get-claimed-inforead-onlyvault-id: uint
fund-rewards-poolpublicamount: uint
process-reward-claimpublicvault-id: uint, recipient: principal, amount: uint
request-claim-rewardspublicvault-id: uint
withdraw-excesspublicamount: uint, recipient: principal