Source Code

;; Time-Locked Vesting - Clarity 4
;; Token vesting with cliff and linear unlock

(define-constant contract-owner tx-sender)
(define-constant err-not-authorized (err u1000))
(define-constant err-not-found (err u1001))
(define-constant err-too-early (err u1002))
(define-constant err-already-claimed (err u1003))

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

(define-map vesting-schedules
    uint
    {
        beneficiary: principal,
        total-amount: uint,
        start-height: uint,
        cliff-height: uint,
        end-height: uint,
        claimed-amount: uint,
        revoked: bool
    }
)

(define-read-only (get-vesting (schedule-id uint))
    (map-get? vesting-schedules schedule-id)
)

(define-read-only (calculate-vested (schedule-id uint))
    (match (get-vesting schedule-id)
        schedule
        (if (< stacks-block-height (get cliff-height schedule))
            u0
            (if (>= stacks-block-height (get end-height schedule))
                (get total-amount schedule)
                (let (
                    (elapsed (- stacks-block-height (get start-height schedule)))
                    (duration (- (get end-height schedule) (get start-height schedule)))
                    (vested (/ (* (get total-amount schedule) elapsed) duration))
                )
                    vested
                )
            )
        )
        u0
    )
)

(define-public (create-vesting
    (beneficiary principal)
    (amount uint)
    (cliff-blocks uint)
    (duration-blocks uint)
)
    (let (
        (schedule-id (var-get vesting-nonce))
        (start stacks-block-height)
        (cliff (+ start cliff-blocks))
        (end (+ start duration-blocks))
    )
        (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
        (map-set vesting-schedules schedule-id {
            beneficiary: beneficiary,
            total-amount: amount,
            start-height: start,
            cliff-height: cliff,
            end-height: end,
            claimed-amount: u0,
            revoked: false
        })
        (var-set vesting-nonce (+ schedule-id u1))
        (ok schedule-id)
    )
)

(define-public (claim-vested (schedule-id uint))
    (let (
        (schedule (unwrap! (get-vesting schedule-id) err-not-found))
        (vested (calculate-vested schedule-id))
        (claimable (- vested (get claimed-amount schedule)))
    )
        (asserts! (is-eq tx-sender (get beneficiary schedule)) err-not-authorized)
        (asserts! (>= stacks-block-height (get cliff-height schedule)) err-too-early)
        (asserts! (> claimable u0) err-already-claimed)
        
        (map-set vesting-schedules schedule-id 
            (merge schedule {claimed-amount: vested})
        )
        (try! (as-contract (stx-transfer? claimable tx-sender (get beneficiary schedule))))
        (ok claimable)
    )
)

(define-public (revoke-vesting (schedule-id uint))
    (let (
        (schedule (unwrap! (get-vesting schedule-id) err-not-found))
        (vested (calculate-vested schedule-id))
        (unclaimed (- vested (get claimed-amount schedule)))
        (unvested (- (get total-amount schedule) vested))
    )
        (asserts! (is-eq tx-sender contract-owner) err-not-authorized)
        
        ;; Transfer unclaimed vested to beneficiary
        (if (> unclaimed u0)
            (try! (as-contract (stx-transfer? unclaimed tx-sender (get beneficiary schedule))))
            true
        )
        
        ;; Return unvested to owner
        (if (> unvested u0)
            (try! (as-contract (stx-transfer? unvested tx-sender contract-owner)))
            true
        )
        
        (ok (map-set vesting-schedules schedule-id 
            (merge schedule {revoked: true, claimed-amount: vested})
        ))
    )
)

Functions (4)

FunctionAccessArgs
get-vestingread-onlyschedule-id: uint
calculate-vestedread-onlyschedule-id: uint
claim-vestedpublicschedule-id: uint
revoke-vestingpublicschedule-id: uint