Source Code

;; deadman-savings-vault.clar
;; A savings vault with deadman switch functionality
;; Owner deposits funds and must ping periodically
;; If owner fails to ping within timeout, beneficiary can claim all funds

(define-constant ERR-NOT-OWNER u401)
(define-constant ERR-TOO-EARLY u402)
(define-constant ERR-INSUFFICIENT u301)
(define-constant ERR-NOT-BENEFICIARY u403)

;; Deadman switch state
(define-data-var owner principal tx-sender)
(define-data-var beneficiary principal tx-sender)
(define-data-var last-ping uint burn-block-height)
(define-data-var admin principal tx-sender)
(define-data-var timeout uint u50) ;; Default 50 blocks, but owner can change

;; Per-user deposit tracking
(define-map deposits {user: principal} {amount: uint})

;; Track if beneficiary has claimed
(define-data-var has-claimed bool false)

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

(define-read-only (get-state)
  (ok {
    owner: (var-get owner),
    beneficiary: (var-get beneficiary),
    last-ping: (var-get last-ping),
    timeout: (var-get timeout),
    has-claimed: (var-get has-claimed)
  }))

(define-read-only (get-deposit (user principal))
  (default-to u0 (get amount (map-get? deposits {user: user}))))

(define-read-only (get-owner-deposit)
  (get-deposit (var-get owner)))

(define-read-only (is-timeout-reached)
  (>= (- burn-block-height (var-get last-ping)) (var-get timeout)))

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

(define-public (transfer-admin (new-admin principal))
  (begin
    (asserts! (is-eq tx-sender (var-get admin)) (err ERR-NOT-OWNER))
    (var-set admin new-admin)
    (ok true)))

;; ===== OWNER FUNCTIONS =====

(define-public (set-beneficiary (b principal))
  (if (is-eq tx-sender (var-get owner))
    (begin 
      (var-set beneficiary b) 
      (ok true))
    (err ERR-NOT-OWNER)))

(define-public (set-timeout (new-timeout uint))
  (if (is-eq tx-sender (var-get owner))
    (begin 
      (var-set timeout new-timeout) 
      (ok new-timeout))
    (err ERR-NOT-OWNER)))

(define-public (ping)
  (if (is-eq tx-sender (var-get owner))
    (begin 
      (var-set last-ping burn-block-height) 
      (ok burn-block-height))
    (err ERR-NOT-OWNER)))

(define-public (deposit (amount uint))
  (begin
    (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
    (let ((prev (default-to u0 (get amount (map-get? deposits {user: tx-sender})))))
      (map-set deposits {user: tx-sender} {amount: (+ prev amount)})
      (ok (+ prev amount)))))

(define-public (withdraw (amount uint))
  (let ((prev (default-to u0 (get amount (map-get? deposits {user: tx-sender})))))
    (if (>= prev amount)
      (begin
        (map-set deposits {user: tx-sender} {amount: (- prev amount)})
        (try! (as-contract (stx-transfer? amount tx-sender tx-sender)))
        (ok (- prev amount)))
      (err ERR-INSUFFICIENT))))

;; ===== BENEFICIARY FUNCTIONS =====

(define-public (claim-all)
  (let (
    (lp (var-get last-ping))
    (current-owner (var-get owner))
    (current-beneficiary (var-get beneficiary))
    (owner-balance (get-deposit current-owner))
  )
    ;; Check timeout reached
    (asserts! (>= (- burn-block-height lp) (var-get timeout)) (err ERR-TOO-EARLY))
    ;; Check caller is beneficiary
    (asserts! (is-eq tx-sender current-beneficiary) (err ERR-NOT-BENEFICIARY))
    
    ;; Transfer ownership
    (var-set owner current-beneficiary)
    (var-set last-ping burn-block-height)
    (var-set has-claimed true)
    
    ;; Transfer all owner's funds to beneficiary
    (if (> owner-balance u0)
      (begin
        (map-set deposits {user: current-owner} {amount: u0})
        (let ((beneficiary-prev (get-deposit current-beneficiary)))
          (map-set deposits {user: current-beneficiary} {amount: (+ beneficiary-prev owner-balance)})
          (ok {transferred: owner-balance, new-balance: (+ beneficiary-prev owner-balance)})))
      (ok {transferred: u0, new-balance: (get-deposit current-beneficiary)}))))

Functions (11)

FunctionAccessArgs
get-stateread-only
get-depositread-onlyuser: principal
get-owner-depositread-only
is-timeout-reachedread-only
transfer-adminpublicnew-admin: principal
set-beneficiarypublicb: principal
set-timeoutpublicnew-timeout: uint
pingpublic
depositpublicamount: uint
withdrawpublicamount: uint
claim-allpublic