Source Code

;; title: token-vesting
;; version: 1.0.0
;; summary: Token vesting schedules
;; description: Lock tokens with vesting schedules - Clarity 4

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-UNAUTHORIZED (err u4100))
(define-constant ERR-NO-SCHEDULE (err u4101))
(define-constant ERR-NOTHING-TO-CLAIM (err u4102))

;; Data Variables
(define-data-var next-schedule-id uint u1)

;; Data Maps - Using stacks-block-time for Clarity 4
(define-map vesting-schedules uint {
  beneficiary: principal,
  total-amount: uint,
  claimed-amount: uint,
  start-time: uint,       ;; Clarity 4: Unix timestamp
  cliff-duration: uint,
  vesting-duration: uint,
  is-revocable: bool,
  is-revoked: bool
})

;; Public Functions

(define-public (create-vesting-schedule
  (beneficiary principal)
  (amount uint)
  (cliff-duration uint)
  (vesting-duration uint)
  (revocable bool))
  (let (
    (schedule-id (var-get next-schedule-id))
  )
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-UNAUTHORIZED)

    (map-set vesting-schedules schedule-id {
      beneficiary: beneficiary,
      total-amount: amount,
      claimed-amount: u0,
      start-time: stacks-block-time,
      cliff-duration: cliff-duration,
      vesting-duration: vesting-duration,
      is-revocable: revocable,
      is-revoked: false
    })

    (var-set next-schedule-id (+ schedule-id u1))

    (print {
      event: "vesting-schedule-created",
      schedule-id: schedule-id,
      beneficiary: beneficiary,
      amount: amount,
      timestamp: stacks-block-time
    })

    (ok schedule-id)
  )
)

(define-public (claim-vested-tokens (schedule-id uint))
  (let (
    (schedule (unwrap! (map-get? vesting-schedules schedule-id) ERR-NO-SCHEDULE))
    (vested-amount (try! (calculate-vested-amount schedule-id)))
    (claimable (- vested-amount (get claimed-amount schedule)))
  )
    (asserts! (is-eq tx-sender (get beneficiary schedule)) ERR-UNAUTHORIZED)
    (asserts! (> claimable u0) ERR-NOTHING-TO-CLAIM)
    (asserts! (not (get is-revoked schedule)) ERR-UNAUTHORIZED)

    (map-set vesting-schedules schedule-id
      (merge schedule {
        claimed-amount: (+ (get claimed-amount schedule) claimable)
      }))

    (print {
      event: "vested-tokens-claimed",
      schedule-id: schedule-id,
      amount: claimable,
      timestamp: stacks-block-time
    })

    (ok claimable)
  )
)

;; Private Functions

(define-private (calculate-vested-amount (schedule-id uint))
  (let (
    (schedule (unwrap! (map-get? vesting-schedules schedule-id) (err u0)))
    (elapsed (- stacks-block-time (get start-time schedule)))
    (cliff-time (get cliff-duration schedule))
    (vesting-time (get vesting-duration schedule))
    (total (get total-amount schedule))
  )
    (if (< elapsed cliff-time)
      (ok u0)
      (if (>= elapsed vesting-time)
        (ok total)
        (ok (/ (* total elapsed) vesting-time))
      )
    )
  )
)

;; Read-Only Functions

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

(define-read-only (get-vested-amount (schedule-id uint))
  (calculate-vested-amount schedule-id)
)

Functions (5)

FunctionAccessArgs
create-vesting-schedulepublicbeneficiary: principal, amount: uint, cliff-duration: uint, vesting-duration: uint, revocable: bool
claim-vested-tokenspublicschedule-id: uint
calculate-vested-amountprivateschedule-id: uint
get-vesting-scheduleread-onlyschedule-id: uint
get-vested-amountread-onlyschedule-id: uint