Source Code

;; timelock.clar
;; Funds locking system with release conditions

;; Constants
(define-constant CONTRACT-ADDRESS .timelock)
(define-constant ERR-LOCK-NOT-FOUND (err u100))
(define-constant ERR-NOT-CREATOR (err u101))
(define-constant ERR-NOT-BENEFICIARY (err u102))
(define-constant ERR-LOCK-NOT-READY (err u103))
(define-constant ERR-ALREADY-RELEASED (err u104))
(define-constant ERR-ZERO-AMOUNT (err u105))

;; Data Variables
(define-data-var lock-counter uint u0)

;; Maps
(define-map locks uint 
  {
    creator: principal,
    beneficiary: principal,
    amount: uint,
    unlock-time: uint,
    released: bool,
    cancelled: bool
  }
)

;; Public Functions
(define-public (create-lock (beneficiary principal) (unlock-time uint) (amount uint))
  (let ((lock-id (+ (var-get lock-counter) u1)))
    (begin
      (asserts! (> amount u0) ERR-ZERO-AMOUNT)
      (asserts! (not (is-eq beneficiary tx-sender)) (err u107))
      (asserts! (> unlock-time stacks-block-time) (err u106))
      
      (unwrap-panic (stx-transfer? amount tx-sender CONTRACT-ADDRESS))
      
      (map-set locks lock-id {
        creator: tx-sender,
        beneficiary: beneficiary,
        amount: amount,
        unlock-time: unlock-time,
        released: false,
        cancelled: false
      })
      
      (var-set lock-counter lock-id)
      (ok lock-id)
    )
  )
)

(define-public (release (lock-id uint))
  (let ((lock (unwrap! (map-get? locks lock-id) ERR-LOCK-NOT-FOUND)))
    (begin
      (asserts! (is-eq (get beneficiary lock) tx-sender) ERR-NOT-BENEFICIARY)
      (asserts! (not (get released lock)) ERR-ALREADY-RELEASED)
      (asserts! (not (get cancelled lock)) ERR-ALREADY-RELEASED)
      (asserts! (>= stacks-block-time (get unlock-time lock)) ERR-LOCK-NOT-READY)
      
      (map-set locks lock-id (merge lock { released: true }))
      (try! (stx-transfer? (get amount lock) CONTRACT-ADDRESS (get beneficiary lock)))
      (ok true)
    )
  )
)

(define-public (cancel (lock-id uint))
  (let ((lock (unwrap! (map-get? locks lock-id) ERR-LOCK-NOT-FOUND)))
    (begin
      (asserts! (is-eq (get creator lock) tx-sender) ERR-NOT-CREATOR)
      (asserts! (not (get released lock)) ERR-ALREADY-RELEASED)
      (asserts! (not (get cancelled lock)) ERR-ALREADY-RELEASED)
      
      (map-set locks lock-id (merge lock { cancelled: true }))
      (try! (stx-transfer? (get amount lock) CONTRACT-ADDRESS (get creator lock)))
      (ok true)
    )
  )
)

;; Read-only
(define-read-only (get-lock (lock-id uint))
  (map-get? locks lock-id)
)

;; CLARITY 4 FEATURE: to-ascii? for lock status
(define-read-only (get-lock-status-ascii (lock-id uint))
  (match (map-get? locks lock-id)
    lock (let (
      (amount-ascii (match (to-ascii? (get amount lock)) ok-val ok-val err "0"))
      (time-remaining (if (> (get unlock-time lock) stacks-block-time)
        (- (get unlock-time lock) stacks-block-time)
        u0))
      (time-ascii (match (to-ascii? time-remaining) ok-val ok-val err "0"))
      (status-str (if (get released lock) "RELEASED"
        (if (get cancelled lock) "CANCELLED"
        (if (> time-remaining u0) "LOCKED" "READY"))))
    )
      (ok {
        beneficiary: (get beneficiary lock),
        amount: amount-ascii,
        time-remaining: time-ascii,
        status: status-str
      })
    )
    (ok {
        beneficiary: tx-sender,
        amount: "0",
        time-remaining: "0",
        status: "NOT_FOUND"
    })
  )
)

Functions (5)

FunctionAccessArgs
create-lockpublicbeneficiary: principal, unlock-time: uint, amount: uint
releasepubliclock-id: uint
cancelpubliclock-id: uint
get-lockread-onlylock-id: uint
get-lock-status-asciiread-onlylock-id: uint