Source Code

;; Aegis Staking v3 - Core Staking Contract
;; Handles stake deposits and position tracking only
;; Part of the Aegis Vault split architecture

;; ============================================
;; CONSTANTS
;; ============================================

(define-constant CONTRACT-OWNER tx-sender)

;; Error codes
(define-constant ERR-NOT-AUTHORIZED (err u4001))
(define-constant ERR-INVALID-AMOUNT (err u4002))
(define-constant ERR-INVALID-LOCK-PERIOD (err u4003))
(define-constant ERR-CONTRACT-PAUSED (err u4004))
(define-constant ERR-MAX-STAKES-REACHED (err u4005))

;; Staking parameters
(define-constant MIN-STAKE u10000)           ;; 0.01 STX minimum
(define-constant MAX-STAKES-PER-USER u20)    ;; Max 20 concurrent stakes
(define-constant BLOCKS-PER-DAY u144)        ;; ~144 blocks per day

;; Lock periods (in days, converted to blocks on-chain)
(define-constant LOCK-3-DAYS u3)
(define-constant LOCK-7-DAYS u7)
(define-constant LOCK-30-DAYS u30)

;; Bonus multipliers (basis points, 10000 = 1x)
(define-constant BONUS-3-DAYS u10000)        ;; 1.0x
(define-constant BONUS-7-DAYS u12000)        ;; 1.2x  
(define-constant BONUS-30-DAYS u15000)       ;; 1.5x

;; ============================================
;; DATA VARIABLES
;; ============================================

(define-data-var contract-paused bool false)
(define-data-var total-staked uint u0)
(define-data-var total-stakers uint u0)
(define-data-var stake-counter uint u0)
(define-data-var withdrawals-contract principal CONTRACT-OWNER)
(define-data-var rewards-contract principal CONTRACT-OWNER)

;; ============================================
;; DATA MAPS
;; ============================================

;; Individual stake positions
(define-map stakes 
  { staker: principal, stake-id: uint }
  {
    amount: uint,
    start-block: uint,
    lock-blocks: uint,
    lock-period-type: uint,
    bonus-multiplier: uint,
    is-active: bool,
    total-claimed: uint
  }
)

;; Track user's stake IDs
(define-map user-stake-ids principal (list 20 uint))

;; Track user's total staked
(define-map user-total-staked principal uint)

;; Authorized contracts that can update stake data
(define-map authorized-contracts principal bool)

;; ============================================
;; AUTHORIZATION
;; ============================================

(define-private (is-authorized)
  (or 
    (is-eq tx-sender CONTRACT-OWNER)
    (default-to false (map-get? authorized-contracts contract-caller))
  )
)

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

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

(define-public (authorize-contract (contract principal) (authorized bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (map-set authorized-contracts contract authorized)
    (ok true)
  )
)

(define-public (set-withdrawals-contract (contract principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set withdrawals-contract contract)
    (map-set authorized-contracts contract true)
    (ok true)
  )
)

(define-public (set-rewards-contract (contract principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set rewards-contract contract)
    (map-set authorized-contracts contract true)
    (ok true)
  )
)

;; ============================================
;; PRIVATE HELPERS
;; ============================================

(define-private (get-lock-blocks (lock-period uint))
  (* lock-period BLOCKS-PER-DAY)
)

(define-private (get-bonus-multiplier (lock-period uint))
  (if (is-eq lock-period LOCK-30-DAYS)
    BONUS-30-DAYS
    (if (is-eq lock-period LOCK-7-DAYS)
      BONUS-7-DAYS
      BONUS-3-DAYS
    )
  )
)

(define-private (is-valid-lock-period (lock-period uint))
  (or 
    (is-eq lock-period LOCK-3-DAYS)
    (or 
      (is-eq lock-period LOCK-7-DAYS)
      (is-eq lock-period LOCK-30-DAYS)
    )
  )
)

;; ============================================
;; PUBLIC FUNCTIONS
;; ============================================

;; Stake STX with a lock period
(define-public (stake (amount uint) (lock-period uint))
  (let
    (
      (staker tx-sender)
      (current-stakes (default-to (list) (map-get? user-stake-ids staker)))
      (new-stake-id (+ (var-get stake-counter) u1))
      (lock-blocks (get-lock-blocks lock-period))
      (bonus (get-bonus-multiplier lock-period))
    )
    ;; Validations
    (asserts! (not (var-get contract-paused)) ERR-CONTRACT-PAUSED)
    (asserts! (>= amount MIN-STAKE) ERR-INVALID-AMOUNT)
    (asserts! (is-valid-lock-period lock-period) ERR-INVALID-LOCK-PERIOD)
    (asserts! (< (len current-stakes) MAX-STAKES-PER-USER) ERR-MAX-STAKES-REACHED)
    
    ;; Transfer STX to this contract
    (try! (stx-transfer? amount staker (as-contract tx-sender)))
    
    ;; Create stake record
    (map-set stakes
      { staker: staker, stake-id: new-stake-id }
      {
        amount: amount,
        start-block: block-height,
        lock-blocks: lock-blocks,
        lock-period-type: lock-period,
        bonus-multiplier: bonus,
        is-active: true,
        total-claimed: u0
      }
    )
    
    ;; Update user's stake list
    (map-set user-stake-ids staker 
      (unwrap! (as-max-len? (append current-stakes new-stake-id) u20) ERR-MAX-STAKES-REACHED))
    
    ;; Update totals
    (map-set user-total-staked staker 
      (+ (default-to u0 (map-get? user-total-staked staker)) amount))
    (var-set total-staked (+ (var-get total-staked) amount))
    (var-set stake-counter new-stake-id)
    
    ;; Increment stakers if first stake
    (if (is-eq (len current-stakes) u0)
      (var-set total-stakers (+ (var-get total-stakers) u1))
      true
    )
    
    (print { 
      event: "stake",
      staker: staker,
      stake-id: new-stake-id,
      amount: amount,
      lock-period: lock-period,
      lock-blocks: lock-blocks,
      unlock-block: (+ block-height lock-blocks)
    })
    
    (ok new-stake-id)
  )
)

;; Called by withdrawals contract to deactivate stake
(define-public (deactivate-stake (staker principal) (stake-id uint) (amount uint))
  (let
    (
      (stake-data (unwrap! (map-get? stakes { staker: staker, stake-id: stake-id }) ERR-INVALID-AMOUNT))
    )
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (asserts! (get is-active stake-data) ERR-INVALID-AMOUNT)
    
    ;; Deactivate stake
    (map-set stakes
      { staker: staker, stake-id: stake-id }
      (merge stake-data { is-active: false })
    )
    
    ;; Update totals
    (map-set user-total-staked staker 
      (- (default-to u0 (map-get? user-total-staked staker)) amount))
    (var-set total-staked (- (var-get total-staked) amount))
    
    (ok true)
  )
)

;; Called by rewards contract to update claimed amount
(define-public (update-claimed (staker principal) (stake-id uint) (claimed-amount uint))
  (let
    (
      (stake-data (unwrap! (map-get? stakes { staker: staker, stake-id: stake-id }) ERR-INVALID-AMOUNT))
    )
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    
    (map-set stakes
      { staker: staker, stake-id: stake-id }
      (merge stake-data { total-claimed: (+ (get total-claimed stake-data) claimed-amount) })
    )
    
    (ok true)
  )
)

;; Transfer STX out (called by withdrawals contract)
(define-public (transfer-stx (recipient principal) (amount uint))
  (begin
    (asserts! (is-authorized) ERR-NOT-AUTHORIZED)
    (as-contract (stx-transfer? amount tx-sender recipient))
  )
)

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

(define-read-only (get-stake (staker principal) (stake-id uint))
  (map-get? stakes { staker: staker, stake-id: stake-id })
)

(define-read-only (get-user-stake-ids (user principal))
  (default-to (list) (map-get? user-stake-ids user))
)

(define-read-only (get-user-total-staked (user principal))
  (default-to u0 (map-get? user-total-staked user))
)

(define-read-only (is-stake-unlocked (staker principal) (stake-id uint))
  (match (map-get? stakes { staker: staker, stake-id: stake-id })
    stake-data
      (>= block-height (+ (get start-block stake-data) (get lock-blocks stake-data)))
    false
  )
)

(define-read-only (get-unlock-block (staker principal) (stake-id uint))
  (match (map-get? stakes { staker: staker, stake-id: stake-id })
    stake-data
      (ok (+ (get start-block stake-data) (get lock-blocks stake-data)))
    (err u0)
  )
)

(define-read-only (get-vault-stats)
  {
    total-staked: (var-get total-staked),
    total-stakers: (var-get total-stakers),
    stake-counter: (var-get stake-counter),
    is-paused: (var-get contract-paused),
    vault-balance: (stx-get-balance (as-contract tx-sender))
  }
)

(define-read-only (is-contract-authorized (contract principal))
  (default-to false (map-get? authorized-contracts contract))
)

(define-read-only (get-withdrawals-contract)
  (var-get withdrawals-contract)
)

(define-read-only (get-rewards-contract)
  (var-get rewards-contract)
)

Functions (21)

FunctionAccessArgs
is-authorizedprivate
set-pausedpublicpaused: bool
authorize-contractpubliccontract: principal, authorized: bool
set-withdrawals-contractpubliccontract: principal
set-rewards-contractpubliccontract: principal
get-lock-blocksprivatelock-period: uint
get-bonus-multiplierprivatelock-period: uint
is-valid-lock-periodprivatelock-period: uint
stakepublicamount: uint, lock-period: uint
deactivate-stakepublicstaker: principal, stake-id: uint, amount: uint
update-claimedpublicstaker: principal, stake-id: uint, claimed-amount: uint
transfer-stxpublicrecipient: principal, amount: uint
get-stakeread-onlystaker: principal, stake-id: uint
get-user-stake-idsread-onlyuser: principal
get-user-total-stakedread-onlyuser: principal
is-stake-unlockedread-onlystaker: principal, stake-id: uint
get-unlock-blockread-onlystaker: principal, stake-id: uint
get-vault-statsread-only
is-contract-authorizedread-onlycontract: principal
get-withdrawals-contractread-only
get-rewards-contractread-only