Source Code

;; Token Vesting Contract
;; Linear vesting schedule for tokens

(define-constant contract-owner tx-sender)
(define-constant err-not-found (err u100))
(define-constant err-not-authorized (err u101))
(define-constant err-not-started (err u102))
(define-constant err-no-vested-tokens (err u103))

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

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

;; Create vesting schedule
(define-public (create-vesting
    (beneficiary principal)
    (total-amount uint)
    (start-block uint)
    (cliff-duration uint)
    (vesting-duration uint)
    (revocable bool))
    (let
        (
            (vesting-id (var-get vesting-id-nonce))
        )
        (asserts! (is-eq tx-sender contract-owner) err-not-authorized)
        
        (try! (stx-transfer? total-amount tx-sender (as-contract tx-sender)))
        
        (map-set vesting-schedules vesting-id {
            beneficiary: beneficiary,
            total-amount: total-amount,
            claimed-amount: u0,
            start-block: start-block,
            cliff-duration: cliff-duration,
            vesting-duration: vesting-duration,
            revocable: revocable,
            revoked: false
        })
        
        (var-set vesting-id-nonce (+ vesting-id u1))
        (ok vesting-id)
    )
)

;; Claim vested tokens
(define-public (claim-vested (vesting-id uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules vesting-id) err-not-found))
            (vested-amount (calculate-vested-amount vesting-id))
            (claimable (- vested-amount (get claimed-amount schedule)))
        )
        (asserts! (is-eq tx-sender (get beneficiary schedule)) err-not-authorized)
        (asserts! (not (get revoked schedule)) err-not-found)
        (asserts! (> claimable u0) err-no-vested-tokens)
        
        (try! (as-contract (stx-transfer? claimable tx-sender (get beneficiary schedule))))
        
        (map-set vesting-schedules vesting-id (merge schedule {
            claimed-amount: (+ (get claimed-amount schedule) claimable)
        }))
        
        (ok claimable)
    )
)

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

;; Calculate vested amount
(define-read-only (calculate-vested-amount (vesting-id uint))
    (match (map-get? vesting-schedules vesting-id)
        schedule
        (let
            (
                (cliff-end (+ (get start-block schedule) (get cliff-duration schedule)))
                (vesting-end (+ (get start-block schedule) (get vesting-duration schedule)))
            )
            (if (get revoked schedule)
                (get claimed-amount schedule)
                (if (< block-height cliff-end)
                    u0
                    (if (>= block-height vesting-end)
                        (get total-amount schedule)
                        (/ (* (get total-amount schedule) 
                              (- block-height (get start-block schedule)))
                           (get vesting-duration schedule))
                    )
                )
            )
        )
        u0
    )
)

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

;; Get claimable amount
(define-read-only (get-claimable-amount (vesting-id uint))
    (match (map-get? vesting-schedules vesting-id)
        schedule
        (- (calculate-vested-amount vesting-id) (get claimed-amount schedule))
        u0
    )
)

Functions (6)

FunctionAccessArgs
create-vestingpublicbeneficiary: principal, total-amount: uint, start-block: uint, cliff-duration: uint, vesting-duration: uint, revocable: bool
claim-vestedpublicvesting-id: uint
revoke-vestingpublicvesting-id: uint
calculate-vested-amountread-onlyvesting-id: uint
get-vesting-scheduleread-onlyvesting-id: uint
get-claimable-amountread-onlyvesting-id: uint