Source Code

;; -------------------------------------------------------
;; TimeFi Emergency Contract v-A2
;; Emergency withdrawals with penalty + Guardian system
;; -------------------------------------------------------

(define-constant ERR_UNAUTHORIZED (err u400))
(define-constant ERR_NOT_FOUND (err u401))
(define-constant ERR_ALREADY_WITHDRAWN (err u402))
(define-constant ERR_COOLDOWN (err u403))
(define-constant ERR_NOT_GUARDIAN (err u404))
(define-constant ERR_EMERGENCY_ACTIVE (err u405))
(define-constant ERR_NO_EMERGENCY (err u406))
(define-constant ERR_INSUFFICIENT (err u407))

(define-constant DEPLOYER tx-sender)

;; Emergency withdrawal penalty (25%)
(define-constant EMERGENCY_PENALTY_BPS u2500)

;; Cooldown between emergency withdrawals (in blocks, 1 block ~ 10 min)
(define-constant EMERGENCY_COOLDOWN u144)  ;; 24 hours (~144 blocks)

;; Guardian threshold for emergency actions
(define-constant GUARDIAN_THRESHOLD u2)  ;; Need 2 guardians to approve

(define-data-var emergency-mode bool false)
(define-data-var emergency-start uint u0)
(define-data-var penalty-collected uint u0)
(define-data-var guardian-count uint u0)

(define-map guardians
  { guardian: principal }
  { active: bool, added-at: uint })

(define-map user-emergency-cooldown
  { user: principal }
  { last-emergency: uint })

(define-map emergency-approvals
  { action-id: uint }
  { approvals: uint, executed: bool })

(define-map guardian-approval-votes
  { action-id: uint, guardian: principal }
  { approved: bool })

(define-data-var action-nonce uint u0)

;; -------------------------------------------------------
;; READ: GET EMERGENCY STATUS
;; -------------------------------------------------------

(define-read-only (is-emergency-mode)
  (var-get emergency-mode))

(define-read-only (get-emergency-start)
  (var-get emergency-start))

;; -------------------------------------------------------
;; READ: GET PENALTY INFO
;; -------------------------------------------------------

(define-read-only (get-penalty-bps)
  EMERGENCY_PENALTY_BPS)

(define-read-only (get-total-penalty-collected)
  (ok (var-get penalty-collected)))

;; -------------------------------------------------------
;; READ: CALCULATE EMERGENCY WITHDRAWAL AMOUNT
;; -------------------------------------------------------

(define-read-only (calculate-emergency-payout (vault-id uint))
  (let (
    (vault-result (contract-call? .timefi-vault-v-A2 get-vault vault-id))
  )
    (match vault-result
      vault
        (let (
          (amount (get amount vault))
          (penalty (/ (* amount EMERGENCY_PENALTY_BPS) u10000))
          (payout (- amount penalty))
        )
          (ok { payout: payout, penalty: penalty, original: amount }))
      error ERR_NOT_FOUND)))

;; -------------------------------------------------------
;; READ: CHECK IF USER CAN EMERGENCY WITHDRAW
;; -------------------------------------------------------

(define-read-only (can-emergency-withdraw (user principal))
  (let (
    (cooldown-data (map-get? user-emergency-cooldown { user: user }))
  )
    (match cooldown-data
      cd (ok (>= tenure-height (+ (get last-emergency cd) EMERGENCY_COOLDOWN)))
      (ok true))))  ;; No previous emergency = allowed

;; -------------------------------------------------------
;; READ: IS GUARDIAN
;; -------------------------------------------------------

(define-read-only (is-guardian (addr principal))
  (default-to false (get active (map-get? guardians { guardian: addr }))))

;; -------------------------------------------------------
;; READ: GET GUARDIAN COUNT
;; -------------------------------------------------------

(define-read-only (get-guardian-count)
  (ok (var-get guardian-count)))

;; -------------------------------------------------------
;; READ: GET ACTION APPROVALS
;; -------------------------------------------------------

(define-read-only (get-action-approvals (action-id uint))
  (default-to { approvals: u0, executed: false }
    (map-get? emergency-approvals { action-id: action-id })))

;; -------------------------------------------------------
;; PUBLIC: ADD GUARDIAN (admin only)
;; -------------------------------------------------------

(define-public (add-guardian (guardian principal))
  (begin
    (asserts! (is-eq tx-sender DEPLOYER) ERR_UNAUTHORIZED)
    (asserts! (not (is-guardian guardian)) ERR_UNAUTHORIZED)
    
    (map-set guardians { guardian: guardian }
      { active: true, added-at: tenure-height })
    
    (var-set guardian-count (+ (var-get guardian-count) u1))
    
    (print { event: "guardian-added", guardian: guardian })
    (ok true)))

;; -------------------------------------------------------
;; PUBLIC: REMOVE GUARDIAN (admin only)
;; -------------------------------------------------------

(define-public (remove-guardian (guardian principal))
  (begin
    (asserts! (is-eq tx-sender DEPLOYER) ERR_UNAUTHORIZED)
    (asserts! (is-guardian guardian) ERR_NOT_GUARDIAN)
    
    (map-set guardians { guardian: guardian }
      { active: false, added-at: u0 })
    
    (var-set guardian-count (- (var-get guardian-count) u1))
    
    (print { event: "guardian-removed", guardian: guardian })
    (ok true)))

;; -------------------------------------------------------
;; PUBLIC: INITIATE EMERGENCY MODE (guardian multisig)
;; -------------------------------------------------------

(define-public (initiate-emergency)
  (let (
    (action-id (+ (var-get action-nonce) u1))
  )
    (asserts! (is-guardian tx-sender) ERR_NOT_GUARDIAN)
    (asserts! (not (var-get emergency-mode)) ERR_EMERGENCY_ACTIVE)
    
    ;; Create new action
    (map-set emergency-approvals { action-id: action-id }
      { approvals: u1, executed: false })
    
    (map-set guardian-approval-votes { action-id: action-id, guardian: tx-sender }
      { approved: true })
    
    (var-set action-nonce action-id)
    
    (print { event: "emergency-initiated", action-id: action-id, initiator: tx-sender })
    (ok action-id)))

;; -------------------------------------------------------
;; PUBLIC: APPROVE EMERGENCY (guardian)
;; -------------------------------------------------------

(define-public (approve-emergency (action-id uint))
  (let (
    (approval-data (get-action-approvals action-id))
    (has-voted (default-to { approved: false }
                 (map-get? guardian-approval-votes { action-id: action-id, guardian: tx-sender })))
  )
    (asserts! (is-guardian tx-sender) ERR_NOT_GUARDIAN)
    (asserts! (not (var-get emergency-mode)) ERR_EMERGENCY_ACTIVE)
    (asserts! (not (get approved has-voted)) ERR_UNAUTHORIZED)
    (asserts! (not (get executed approval-data)) ERR_ALREADY_WITHDRAWN)
    
    (let (
      (new-approvals (+ (get approvals approval-data) u1))
    )
      ;; Record vote
      (map-set guardian-approval-votes { action-id: action-id, guardian: tx-sender }
        { approved: true })
      
      (map-set emergency-approvals { action-id: action-id }
        { approvals: new-approvals, executed: (get executed approval-data) })
      
      ;; Check threshold
      (if (>= new-approvals GUARDIAN_THRESHOLD)
        (begin
          (var-set emergency-mode true)
          (var-set emergency-start tenure-height)
          (map-set emergency-approvals { action-id: action-id }
            { approvals: new-approvals, executed: true })
          (print { event: "emergency-activated", action-id: action-id, approvals: new-approvals })
          (ok true))
        (begin
          (print { event: "emergency-approved", action-id: action-id, guardian: tx-sender, approvals: new-approvals })
          (ok true))))))

;; -------------------------------------------------------
;; PUBLIC: DEACTIVATE EMERGENCY (admin only)
;; -------------------------------------------------------

(define-public (deactivate-emergency)
  (begin
    (asserts! (is-eq tx-sender DEPLOYER) ERR_UNAUTHORIZED)
    (asserts! (var-get emergency-mode) ERR_NO_EMERGENCY)
    
    (var-set emergency-mode false)
    (var-set emergency-start u0)
    
    (print { event: "emergency-deactivated" })
    (ok true)))

;; -------------------------------------------------------
;; PUBLIC: EMERGENCY WITHDRAW REQUEST (tracks intent)
;; Note: Actual withdrawal must be done through vault contract
;; This contract tracks penalties and cooldowns
;; -------------------------------------------------------

(define-public (request-emergency-withdraw (vault-id uint))
  (let (
    (vault-data (unwrap! (contract-call? .timefi-vault-v-A2 get-vault vault-id) ERR_NOT_FOUND))
    (amount (get amount vault-data))
    (penalty (/ (* amount EMERGENCY_PENALTY_BPS) u10000))
    (payout (- amount penalty))
    (can-withdraw (unwrap! (can-emergency-withdraw tx-sender) ERR_COOLDOWN))
  )
    (asserts! (is-eq (get owner vault-data) tx-sender) ERR_UNAUTHORIZED)
    (asserts! (get active vault-data) ERR_ALREADY_WITHDRAWN)
    (asserts! can-withdraw ERR_COOLDOWN)
    (asserts! (> amount u0) ERR_INSUFFICIENT)
    
    ;; Record cooldown
    (map-set user-emergency-cooldown { user: tx-sender }
      { last-emergency: tenure-height })
    
    ;; Track penalty
    (if (not (var-get emergency-mode))
      (var-set penalty-collected (+ (var-get penalty-collected) penalty))
      true)
    
    (print { 
      event: "emergency-withdraw-request", 
      vault-id: vault-id, 
      user: tx-sender, 
      amount: amount, 
      penalty: (if (var-get emergency-mode) u0 penalty),
      payout: (if (var-get emergency-mode) amount payout),
      mode: (if (var-get emergency-mode) "emergency" "penalty")
    })
    (ok { payout: (if (var-get emergency-mode) amount payout), penalty: (if (var-get emergency-mode) u0 penalty) })))

;; -------------------------------------------------------
;; PUBLIC: WITHDRAW PENALTY POOL (admin only)
;; -------------------------------------------------------

(define-public (withdraw-penalty-pool (amount uint) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender DEPLOYER) ERR_UNAUTHORIZED)
    (asserts! (<= amount (var-get penalty-collected)) ERR_INSUFFICIENT)
    
    (var-set penalty-collected (- (var-get penalty-collected) amount))
    
    (print { event: "penalty-withdrawn", amount: amount, recipient: recipient })
    (ok true)))

;; -------------------------------------------------------
;; READ: GET USER COOLDOWN STATUS
;; -------------------------------------------------------

(define-read-only (get-user-cooldown (user principal))
  (match (map-get? user-emergency-cooldown { user: user })
    cd (ok {
          last-emergency: (get last-emergency cd),
          cooldown-ends: (+ (get last-emergency cd) EMERGENCY_COOLDOWN),
          can-withdraw: (>= tenure-height (+ (get last-emergency cd) EMERGENCY_COOLDOWN))
        })
    (ok { last-emergency: u0, cooldown-ends: u0, can-withdraw: true })))

Functions (17)

FunctionAccessArgs
is-emergency-moderead-only
get-emergency-startread-only
get-penalty-bpsread-only
get-total-penalty-collectedread-only
calculate-emergency-payoutread-onlyvault-id: uint
can-emergency-withdrawread-onlyuser: principal
is-guardianread-onlyaddr: principal
get-guardian-countread-only
get-action-approvalsread-onlyaction-id: uint
add-guardianpublicguardian: principal
remove-guardianpublicguardian: principal
initiate-emergencypublic
approve-emergencypublicaction-id: uint
deactivate-emergencypublic
request-emergency-withdrawpublicvault-id: uint
withdraw-penalty-poolpublicamount: uint, recipient: principal
get-user-cooldownread-onlyuser: principal