Source Code

;; deadman-vault-core-v2
;; Primary entry point for the Deadman Protocol (V2).
;; Users create vaults, configure conditions, deposit STX, and trigger releases here.
;; Orchestrates delegation, fee collection, and vault registration.
;; STX is held by this contract and released directly to beneficiaries.

(define-constant ERR-PAUSED (err u601))
(define-constant ERR-INVALID-CONDITION (err u602))
(define-constant ERR-VAULT-NOT-FOUND (err u603))
(define-constant ERR-NOT-VAULT-OWNER (err u604))
(define-constant ERR-NOT-ACTIVE (err u605))
(define-constant ERR-CONDITIONS-NOT-MET (err u606))
(define-constant ERR-LOCK-TOO-SHORT (err u607))
(define-constant ERR-ZERO-DEPOSIT (err u608))
(define-constant ERR-NO-BENEFICIARY (err u609))
(define-constant ERR-CANCEL-FAILED (err u610))
(define-constant ERR-TRANSFER-FAILED (err u611))

;; Vault status constants
(define-constant STATUS-ACTIVE u0)
(define-constant STATUS-RELEASED u1)
(define-constant STATUS-CANCELLED u2)

(define-data-var next-vault-id uint u1)

(define-map vaults uint {
  owner: principal,
  amount: uint,
  condition-type: uint,
  target-block: uint,
  inactivity-blocks: uint,
  required-threshold: uint,
  status: uint,
  created-at: uint
})

;; Index: owner -> vault-id at position
(define-map owner-vault-ids { owner: principal, index: uint } uint)
(define-map owner-vault-count principal uint)

;; --- Vault Creation ---

(define-public (create-vault
    (amount uint)
    (condition-type uint)
    (target-block uint)
    (inactivity-blocks uint)
    (required-threshold uint)
    (beneficiary principal))
  (let (
    (vault-id (var-get next-vault-id))
    (cfg (contract-call? .admin-config get-config))
    (min-lock (get min-lock-blocks cfg)))
    (asserts! (not (get paused cfg)) ERR-PAUSED)
    (asserts! (> amount u0) ERR-ZERO-DEPOSIT)
    (asserts! (and (>= condition-type u1) (<= condition-type u3)) ERR-INVALID-CONDITION)
    (asserts!
      (if (is-eq condition-type u1)
          (>= target-block (+ block-height min-lock))
          (if (is-eq condition-type u2)
              (>= inactivity-blocks min-lock)
              (> required-threshold u0)))
      ERR-LOCK-TOO-SHORT)
    ;; Collect protocol fee (charged on top of deposit)
    (try! (contract-call? .deadman-fee-vault collect-fee amount))
    ;; Transfer deposit to vault-core
    (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
    ;; Set beneficiary in delegation registry
    (try! (contract-call? .deadman-delegation-registry-v2 set-beneficiary vault-id beneficiary tx-sender))
    ;; Register vault in global registry
    (try! (contract-call? .deadman-vault-registry register-vault vault-id tx-sender beneficiary))
    ;; Auto-ping for inactivity vaults so the countdown starts from creation
    (if (is-eq condition-type u2)
        (begin (unwrap-panic (contract-call? .activity-tracker ping)) true)
        true)
    (map-set vaults vault-id {
      owner: tx-sender,
      amount: amount,
      condition-type: condition-type,
      target-block: target-block,
      inactivity-blocks: inactivity-blocks,
      required-threshold: required-threshold,
      status: STATUS-ACTIVE,
      created-at: block-height
    })
    (map-set owner-vault-ids { owner: tx-sender, index: (default-to u0 (map-get? owner-vault-count tx-sender)) } vault-id)
    (map-set owner-vault-count tx-sender (+ (default-to u0 (map-get? owner-vault-count tx-sender)) u1))
    (var-set next-vault-id (+ vault-id u1))
    (print { event: "vault-created", vault-id: vault-id, owner: tx-sender, amount: amount, condition-type: condition-type })
    (ok vault-id)))

;; --- Add Co-signer (owner only, active vaults) ---

(define-public (add-cosigner (vault-id uint) (cosigner principal))
  (let (
    (vault (unwrap! (map-get? vaults vault-id) ERR-VAULT-NOT-FOUND))
    (max-cosigners (get max-cosigners (contract-call? .admin-config get-config))))
    (asserts! (is-eq tx-sender (get owner vault)) ERR-NOT-VAULT-OWNER)
    (asserts! (is-eq (get status vault) STATUS-ACTIVE) ERR-NOT-ACTIVE)
    (contract-call? .deadman-delegation-registry-v2 add-cosigner vault-id cosigner tx-sender max-cosigners)))

;; --- Submit Co-signer Approval ---

(define-public (submit-approval (vault-id uint))
  (begin
    (asserts! (is-some (map-get? vaults vault-id)) ERR-VAULT-NOT-FOUND)
    (contract-call? .deadman-delegation-registry-v2 submit-approval vault-id)))

;; --- Trigger Release ---
;; Transfers STX directly from this contract to the beneficiary.

(define-public (trigger-release (vault-id uint))
  (let (
    (vault (unwrap! (map-get? vaults vault-id) ERR-VAULT-NOT-FOUND))
    (approval-count (contract-call? .deadman-delegation-registry-v2 get-approval-count vault-id))
    (condition-met (unwrap! (contract-call? .condition-engine evaluate-condition
        (get condition-type vault)
        (get owner vault)
        (get target-block vault)
        (get inactivity-blocks vault)
        approval-count
        (get required-threshold vault))
      ERR-INVALID-CONDITION))
    (beneficiary (unwrap! (contract-call? .deadman-delegation-registry-v2 get-beneficiary vault-id) ERR-NO-BENEFICIARY)))
    (asserts! (is-eq (get status vault) STATUS-ACTIVE) ERR-NOT-ACTIVE)
    (asserts! condition-met ERR-CONDITIONS-NOT-MET)
    (map-set vaults vault-id (merge vault { status: STATUS-RELEASED }))
    ;; Update vault registry
    (try! (contract-call? .deadman-vault-registry update-vault-status vault-id STATUS-RELEASED))
    (match (as-contract (stx-transfer? (get amount vault) tx-sender beneficiary))
      success (begin
        (print { event: "vault-released", vault-id: vault-id, beneficiary: beneficiary, amount: (get amount vault) })
        (ok true))
      error ERR-TRANSFER-FAILED)))

;; --- Cancel Vault (owner only, active vaults) ---

(define-public (cancel-vault (vault-id uint))
  (let ((vault (unwrap! (map-get? vaults vault-id) ERR-VAULT-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get owner vault)) ERR-NOT-VAULT-OWNER)
    (asserts! (is-eq (get status vault) STATUS-ACTIVE) ERR-NOT-ACTIVE)
    (map-set vaults vault-id (merge vault { status: STATUS-CANCELLED }))
    ;; Update vault registry
    (try! (contract-call? .deadman-vault-registry update-vault-status vault-id STATUS-CANCELLED))
    (match (as-contract (stx-transfer? (get amount vault) tx-sender (get owner vault)))
      success (begin
        (print { event: "vault-cancelled", vault-id: vault-id, owner: (get owner vault) })
        (ok true))
      error ERR-CANCEL-FAILED)))

;; --- Read Functions ---

(define-read-only (get-vault (vault-id uint))
  (map-get? vaults vault-id))

(define-read-only (get-vault-status (vault-id uint))
  (get status (map-get? vaults vault-id)))

(define-read-only (get-next-vault-id)
  (var-get next-vault-id))

(define-read-only (get-owner-vault-count (owner principal))
  (default-to u0 (map-get? owner-vault-count owner)))

(define-read-only (get-owner-vault-id (owner principal) (index uint))
  (map-get? owner-vault-ids { owner: owner, index: index }))

Functions (10)

FunctionAccessArgs
create-vaultpublicamount: uint, condition-type: uint, target-block: uint, inactivity-blocks: uint, required-threshold: uint, beneficiary: principal
add-cosignerpublicvault-id: uint, cosigner: principal
submit-approvalpublicvault-id: uint
trigger-releasepublicvault-id: uint
cancel-vaultpublicvault-id: uint
get-vaultread-onlyvault-id: uint
get-vault-statusread-onlyvault-id: uint
get-next-vault-idread-only
get-owner-vault-countread-onlyowner: principal
get-owner-vault-idread-onlyowner: principal, index: uint