Source Code

(define-non-fungible-token vesting-nft uint)

(define-map vesting-schedules
    uint
    {
        payout-token: principal,
        total-amount: uint,
        claimed-amount: uint,
        vesting-start: uint,
        vesting-end: uint
    }
)

(define-map claim-approvals { token-id: uint, operator: principal } bool)
(define-map claim-approvals-for-all { owner: principal, operator: principal } bool)
(define-map token-uris uint (string-ascii 256))

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

(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u100))
(define-constant err-not-token-owner (err u101))
(define-constant err-invalid-token (err u102))
(define-constant err-not-approved (err u103))
(define-constant err-nothing-to-claim (err u104))
(define-constant err-transfer-failed (err u105))

(define-read-only (get-last-token-id)
    (ok (var-get token-id-nonce))
)

(define-read-only (get-token-uri (token-id uint))
    (ok (map-get? token-uris token-id))
)

(define-read-only (get-owner (token-id uint))
    (ok (nft-get-owner? vesting-nft token-id))
)

(define-read-only (get-vesting-period (token-id uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
        )
        (ok { vesting-start: (get vesting-start schedule), vesting-end: (get vesting-end schedule) })
    )
)

(define-read-only (claimed-payout (token-id uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
        )
        (ok (get claimed-amount schedule))
    )
)

(define-read-only (vested-payout (token-id uint))
    (vested-payout-at-time token-id stacks-block-time)
)

(define-read-only (vested-payout-at-time (token-id uint) (timestamp uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
            (vesting-start (get vesting-start schedule))
            (vesting-end (get vesting-end schedule))
            (total-amount (get total-amount schedule))
        )
        (if (<= timestamp vesting-start)
            (ok u0)
            (if (>= timestamp vesting-end)
                (ok total-amount)
                (ok (/ (* total-amount (- timestamp vesting-start)) (- vesting-end vesting-start)))
            )
        )
    )
)

(define-read-only (claimable-payout (token-id uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
            (vested (unwrap-panic (vested-payout token-id)))
            (claimed (get claimed-amount schedule))
        )
        (ok (if (> vested claimed) (- vested claimed) u0))
    )
)

(define-read-only (vesting-payout (token-id uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
            (total (get total-amount schedule))
            (vested (unwrap-panic (vested-payout token-id)))
        )
        (ok (- total vested))
    )
)

(define-read-only (payout-token (token-id uint))
    (let
        (
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
        )
        (ok (get payout-token schedule))
    )
)

(define-read-only (is-claim-approved-for-all (owner principal) (operator principal))
    (ok (default-to false (map-get? claim-approvals-for-all { owner: owner, operator: operator })))
)

(define-read-only (get-claim-approved (token-id uint))
    (ok none)
)

(define-public (transfer (token-id uint) (sender principal) (recipient principal))
    (begin
        (asserts! (is-eq tx-sender sender) err-not-token-owner)
        (nft-transfer? vesting-nft token-id sender recipient)
    )
)

(define-public (mint (recipient principal) (payout-token-contract principal) (total-amount uint) (vesting-start uint) (vesting-end uint))
    (let
        (
            (new-token-id (+ (var-get token-id-nonce) u1))
        )
        (try! (nft-mint? vesting-nft new-token-id recipient))
        (map-set vesting-schedules new-token-id {
            payout-token: payout-token-contract,
            total-amount: total-amount,
            claimed-amount: u0,
            vesting-start: vesting-start,
            vesting-end: vesting-end
        })
        (var-set token-id-nonce new-token-id)
        (ok new-token-id)
    )
)

(define-public (claim (token-id uint))
    (let
        (
            (token-owner (unwrap! (nft-get-owner? vesting-nft token-id) err-invalid-token))
            (schedule (unwrap! (map-get? vesting-schedules token-id) err-invalid-token))
            (claimable (unwrap-panic (claimable-payout token-id)))
        )
        (asserts! (or (is-eq tx-sender token-owner) (is-approved-claimer token-id tx-sender)) err-not-approved)
        (asserts! (> claimable u0) err-nothing-to-claim)
        (map-set vesting-schedules token-id (merge schedule { claimed-amount: (+ (get claimed-amount schedule) claimable) }))
        (print { type: "payout-claimed", token-id: token-id, recipient: token-owner, claim-amount: claimable })
        (ok claimable)
    )
)

(define-public (set-claim-approval-for-all (operator principal) (approved bool))
    (begin
        (ok (map-set claim-approvals-for-all { owner: tx-sender, operator: operator } approved))
    )
)

(define-public (set-claim-approval (operator principal) (approved bool) (token-id uint))
    (let
        (
            (token-owner (unwrap! (nft-get-owner? vesting-nft token-id) err-invalid-token))
        )
        (asserts! (is-eq tx-sender token-owner) err-not-token-owner)
        (ok (map-set claim-approvals { token-id: token-id, operator: operator } approved))
    )
)

(define-private (is-approved-claimer (token-id uint) (operator principal))
    (let
        (
            (token-owner (unwrap! (nft-get-owner? vesting-nft token-id) false))
        )
        (or
            (default-to false (map-get? claim-approvals { token-id: token-id, operator: operator }))
            (default-to false (map-get? claim-approvals-for-all { owner: token-owner, operator: operator }))
        )
    )
)

(define-public (set-token-uri (token-id uint) (uri (string-ascii 256)))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-owner-only)
        (ok (map-set token-uris token-id uri))
    )
)

(define-public (burn (token-id uint))
    (let
        (
            (token-owner (unwrap! (nft-get-owner? vesting-nft token-id) err-invalid-token))
        )
        (asserts! (is-eq tx-sender token-owner) err-not-token-owner)
        (map-delete vesting-schedules token-id)
        (nft-burn? vesting-nft token-id token-owner)
    )
)

Functions (20)

FunctionAccessArgs
get-last-token-idread-only
get-token-uriread-onlytoken-id: uint
get-ownerread-onlytoken-id: uint
get-vesting-periodread-onlytoken-id: uint
claimed-payoutread-onlytoken-id: uint
vested-payoutread-onlytoken-id: uint
vested-payout-at-timeread-onlytoken-id: uint, timestamp: uint
claimable-payoutread-onlytoken-id: uint
vesting-payoutread-onlytoken-id: uint
payout-tokenread-onlytoken-id: uint
is-claim-approved-for-allread-onlyowner: principal, operator: principal
get-claim-approvedread-onlytoken-id: uint
transferpublictoken-id: uint, sender: principal, recipient: principal
mintpublicrecipient: principal, payout-token-contract: principal, total-amount: uint, vesting-start: uint, vesting-end: uint
claimpublictoken-id: uint
set-claim-approval-for-allpublicoperator: principal, approved: bool
set-claim-approvalpublicoperator: principal, approved: bool, token-id: uint
is-approved-claimerprivatetoken-id: uint, operator: principal
set-token-uripublictoken-id: uint, uri: (string-ascii 256
burnpublictoken-id: uint