Source Code

;; Stakied Liquidity Gauge - LP incentive gauge with boost mechanics
;; LP stakers earn boosted rewards based on governance token balance

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u900))
(define-constant err-not-authorized (err u901))
(define-constant err-invalid-amount (err u902))
(define-constant err-insufficient-balance (err u903))
(define-constant err-no-stake (err u904))
(define-constant err-gauge-paused (err u905))
(define-constant err-invalid-rate (err u906))
(define-constant err-no-rewards (err u907))
(define-constant err-invalid-maturity (err u908))
(define-constant err-invalid-boost (err u909))

;; Boost configuration
(define-constant max-boost u2500000)     ;; 2.5x max boost (6 decimals)
(define-constant min-boost u1000000)     ;; 1.0x min boost (6 decimals)
(define-constant boost-precision u1000000)
(define-constant reward-precision u1000000000000) ;; 12 decimals

;; Data variables
(define-data-var emission-rate uint u100)   ;; tokens per block
(define-data-var total-staked-lp uint u0)
(define-data-var global-reward-per-token uint u0)
(define-data-var last-update-block uint u0)
(define-data-var gauge-paused bool false)
(define-data-var total-boosted-supply uint u0)

;; Data maps
(define-map lp-stakes
  principal
  {
    amount: uint,
    maturity: uint,
    boosted-amount: uint,
    reward-per-token-paid: uint,
    pending-rewards: uint,
    staked-at: uint
  }
)

;; Governance token balances for boost calculation
(define-map governance-balances principal uint)

;; Read-only functions
(define-read-only (get-lp-stake (user principal))
  (ok (map-get? lp-stakes user)))

(define-read-only (get-gauge-info)
  (ok {
    emission-rate: (var-get emission-rate),
    total-staked-lp: (var-get total-staked-lp),
    total-boosted-supply: (var-get total-boosted-supply),
    global-reward-per-token: (var-get global-reward-per-token),
    last-update-block: (var-get last-update-block),
    paused: (var-get gauge-paused)
  }))

(define-read-only (get-boost-factor (user principal))
  (let (
    (gov-balance (default-to u0 (map-get? governance-balances user)))
    (user-stake (map-get? lp-stakes user))
  )
    (match user-stake
      stake-data
        (let (
          (staked (get amount stake-data))
          (total (var-get total-staked-lp))
        )
          (if (or (is-eq staked u0) (is-eq total u0))
            (ok min-boost)
            ;; boost = min(2.5, 1.0 + 1.5 * (gov_balance / total_staked))
            (let (
              (gov-ratio (/ (* gov-balance boost-precision) total))
              (bonus (/ (* gov-ratio u1500000) boost-precision))
              (raw-boost (+ min-boost bonus))
            )
              (ok (if (> raw-boost max-boost) max-boost raw-boost))
            )
          )
        )
      (ok min-boost)
    )
  )
)

(define-read-only (get-earned (user principal))
  (let (
    (stake-data (unwrap! (map-get? lp-stakes user) (ok u0)))
    (boosted (get boosted-amount stake-data))
    (paid (get reward-per-token-paid stake-data))
    (pending (get pending-rewards stake-data))
    (current-rpt (calculate-reward-per-token))
  )
    (ok (+ pending (/ (* boosted (- current-rpt paid)) reward-precision)))
  )
)

;; Private helpers
(define-private (calculate-reward-per-token)
  (let (
    (total-boosted (var-get total-boosted-supply))
    (last-block (var-get last-update-block))
    (blocks-elapsed (if (> block-height last-block)
                       (- block-height last-block)
                       u0))
    (new-rewards (* blocks-elapsed (var-get emission-rate)))
  )
    (if (is-eq total-boosted u0)
      (var-get global-reward-per-token)
      (+ (var-get global-reward-per-token)
         (/ (* new-rewards reward-precision) total-boosted))
    )
  )
)

(define-private (update-rewards (user principal))
  (let (
    (new-rpt (calculate-reward-per-token))
  )
    (var-set global-reward-per-token new-rpt)
    (var-set last-update-block block-height)

    (match (map-get? lp-stakes user)
      stake-data
        (let (
          (earned (/ (* (get boosted-amount stake-data) (- new-rpt (get reward-per-token-paid stake-data))) reward-precision))
        )
          (map-set lp-stakes user (merge stake-data {
            pending-rewards: (+ (get pending-rewards stake-data) earned),
            reward-per-token-paid: new-rpt
          }))
        )
      true
    )
  )
)

;; Public functions
(define-public (stake-lp (amount uint) (maturity uint))
  (begin
    (asserts! (not (var-get gauge-paused)) err-gauge-paused)
    (asserts! (> amount u0) err-invalid-amount)
    (asserts! (> maturity u0) err-invalid-maturity)

    ;; Update rewards before state changes
    (update-rewards tx-sender)

    (let (
      (boost (unwrap-panic (get-boost-factor tx-sender)))
      (boosted-amount (/ (* amount boost) boost-precision))
      (existing (map-get? lp-stakes tx-sender))
    )
      (match existing
        prev-stake
          ;; Add to existing stake
          (let (
            (new-amount (+ (get amount prev-stake) amount))
            (new-boosted (+ (get boosted-amount prev-stake) boosted-amount))
          )
            (map-set lp-stakes tx-sender (merge prev-stake {
              amount: new-amount,
              boosted-amount: new-boosted
            }))
            (var-set total-boosted-supply (+ (var-get total-boosted-supply) boosted-amount))
          )
        ;; New stake
        (map-set lp-stakes tx-sender {
          amount: amount,
          maturity: maturity,
          boosted-amount: boosted-amount,
          reward-per-token-paid: (var-get global-reward-per-token),
          pending-rewards: u0,
          staked-at: block-height
        })
      )

      (var-set total-staked-lp (+ (var-get total-staked-lp) amount))
      (match existing
        prev-stake true
        (var-set total-boosted-supply (+ (var-get total-boosted-supply) boosted-amount))
      )

      (print {action: "stake-lp", user: tx-sender, amount: amount, boost: boost, boosted-amount: boosted-amount})
      (ok {amount: amount, boost: boost})
    )
  )
)

(define-public (unstake-lp (amount uint))
  (let (
    (stake-data (unwrap! (map-get? lp-stakes tx-sender) err-no-stake))
    (staked (get amount stake-data))
  )
    (asserts! (> amount u0) err-invalid-amount)
    (asserts! (<= amount staked) err-insufficient-balance)

    ;; Update rewards
    (update-rewards tx-sender)

    (let (
      (boosted-ratio (if (> staked u0) (/ (* (get boosted-amount stake-data) amount) staked) u0))
      (remaining (- staked amount))
    )
      (if (is-eq remaining u0)
        ;; Full unstake - remove entry
        (begin
          (map-delete lp-stakes tx-sender)
          (var-set total-boosted-supply (- (var-get total-boosted-supply) (get boosted-amount stake-data)))
        )
        ;; Partial unstake
        (begin
          (map-set lp-stakes tx-sender (merge stake-data {
            amount: remaining,
            boosted-amount: (- (get boosted-amount stake-data) boosted-ratio)
          }))
          (var-set total-boosted-supply (- (var-get total-boosted-supply) boosted-ratio))
        )
      )

      (var-set total-staked-lp (- (var-get total-staked-lp) amount))

      (print {action: "unstake-lp", user: tx-sender, amount: amount})
      (ok amount)
    )
  )
)

(define-public (claim-gauge-rewards)
  (let (
    (stake-data (unwrap! (map-get? lp-stakes tx-sender) err-no-stake))
  )
    ;; Update rewards
    (update-rewards tx-sender)

    ;; Re-read after update
    (let (
      (updated-stake (unwrap-panic (map-get? lp-stakes tx-sender)))
      (pending (get pending-rewards updated-stake))
    )
      (asserts! (> pending u0) err-no-rewards)

      (map-set lp-stakes tx-sender (merge updated-stake {
        pending-rewards: u0
      }))

      (print {action: "claim-gauge-rewards", user: tx-sender, amount: pending})
      (ok pending)
    )
  )
)

(define-public (set-emission-rate (new-rate uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (asserts! (> new-rate u0) err-invalid-rate)

    ;; Update global state before changing rate
    (var-set global-reward-per-token (calculate-reward-per-token))
    (var-set last-update-block block-height)
    (var-set emission-rate new-rate)

    (print {action: "set-emission-rate", rate: new-rate})
    (ok new-rate)
  )
)

(define-public (set-governance-balance (user principal) (balance uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (map-set governance-balances user balance)
    (print {action: "set-governance-balance", user: user, balance: balance})
    (ok true)
  )
)

(define-public (set-gauge-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender contract-owner) err-owner-only)
    (var-set gauge-paused paused)
    (print {action: "set-gauge-paused", paused: paused})
    (ok paused)
  )
)

Functions (12)

FunctionAccessArgs
get-lp-stakeread-onlyuser: principal
get-gauge-inforead-only
get-boost-factorread-onlyuser: principal
get-earnedread-onlyuser: principal
calculate-reward-per-tokenprivate
update-rewardsprivateuser: principal
stake-lppublicamount: uint, maturity: uint
unstake-lppublicamount: uint
claim-gauge-rewardspublic
set-emission-ratepublicnew-rate: uint
set-governance-balancepublicuser: principal, balance: uint
set-gauge-pausedpublicpaused: bool