Source Code

;; title: TimeVintage
;; version:
;; summary:
;; description:

;; token definitions
(define-non-fungible-token time-nft uint)

;; constants
(define-constant CONTRACT_OWNER tx-sender)
(define-constant ERR_OWNER_ONLY (err u100))
(define-constant ERR_NOT_TOKEN_OWNER (err u101))
(define-constant ERR_NOT_FOUND (err u102))
(define-constant ERR_TIME_LOCKED (err u103))
(define-constant ERR_INVALID_UNLOCK_HEIGHT (err u104))
(define-constant ERR_METADATA_FROZEN (err u105))
(define-constant ERR_ALREADY_UNLOCKED (err u106))
(define-constant ERR_INSUFFICIENT_PAYMENT (err u107))
(define-constant ERR_BATCH_LIMIT_EXCEEDED (err u108))
(define-constant MAX_BATCH_SIZE u10)

;; data vars
(define-data-var last-token-id uint u0)
(define-data-var mint-price uint u1000000)
(define-data-var royalty-percentage uint u250)
(define-data-var contract-paused bool false)

;; data maps
(define-map token-metadata
    { token-id: uint }
    {
        name: (string-ascii 256),
        description: (string-ascii 512),
        image: (string-ascii 256),
        unlock-height: uint,
        locked-content: (string-ascii 1024),
        creator: principal
    }
)

(define-map token-uris { token-id: uint } { uri: (optional (string-ascii 256)) })

(define-map creator-tokens { creator: principal } { token-ids: (list 1000 uint) })

(define-map royalty-recipients { token-id: uint } { recipient: principal, percentage: uint })

(define-public (transfer (token-id uint) (sender principal) (recipient principal))
    (begin
        (asserts! (is-some (nft-get-owner? time-nft token-id)) ERR_NOT_FOUND)
        (asserts! (is-eq tx-sender sender) ERR_NOT_TOKEN_OWNER)
        (asserts! (is-eq (some sender) (nft-get-owner? time-nft token-id)) ERR_NOT_TOKEN_OWNER)
        (nft-transfer? time-nft token-id sender recipient)
    )
)

(define-public (set-token-uri (token-id uint) (uri (optional (string-ascii 256))))
    (begin
        (asserts! (is-some (nft-get-owner? time-nft token-id)) ERR_NOT_FOUND)
        (asserts! (is-eq (some tx-sender) (nft-get-owner? time-nft token-id)) ERR_NOT_TOKEN_OWNER)
        (ok (map-set token-uris { token-id: token-id } { uri: uri }))
    )
)

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

(define-read-only (get-token-uri (token-id uint))
    (ok (match (map-get? token-uris { token-id: token-id })
        token-uri-data (get uri token-uri-data)
        none
    ))
)

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

(define-read-only (get-token-metadata (token-id uint))
    (map-get? token-metadata { token-id: token-id })
)

(define-read-only (is-unlocked (token-id uint))
    (match (get-token-metadata token-id)
        metadata (>= stacks-block-height (get unlock-height metadata))
        false
    )
)

(define-read-only (get-unlocked-content (token-id uint))
    (let ((metadata (unwrap! (get-token-metadata token-id) (err ERR_NOT_FOUND))))
        (if (>= stacks-block-height (get unlock-height metadata))
            (ok (some (get locked-content metadata)))
            (ok none)
        )
    )
)

(define-read-only (get-unlock-height (token-id uint))
    (match (get-token-metadata token-id)
        metadata (ok (get unlock-height metadata))
        (err ERR_NOT_FOUND)
    )
)

(define-read-only (blocks-until-unlock (token-id uint))
    (match (get-token-metadata token-id)
        metadata 
            (let ((unlock-height (get unlock-height metadata)))
                (if (>= stacks-block-height unlock-height)
                    (ok u0)
                    (ok (- unlock-height stacks-block-height))
                )
            )
        (err ERR_NOT_FOUND)
    )
)


(define-public (update-unlock-height (token-id uint) (new-unlock-height uint))
    (let ((metadata (unwrap! (get-token-metadata token-id) ERR_NOT_FOUND)))
        (asserts! (is-eq (some tx-sender) (nft-get-owner? time-nft token-id)) ERR_NOT_TOKEN_OWNER)
        (asserts! (< stacks-block-height (get unlock-height metadata)) ERR_ALREADY_UNLOCKED)
        (asserts! (> new-unlock-height stacks-block-height) ERR_INVALID_UNLOCK_HEIGHT)
        (map-set token-metadata
            { token-id: token-id }
            (merge metadata { unlock-height: new-unlock-height })
        )
        (ok true)
    )
)

(define-public (burn (token-id uint))
    (begin
        (asserts! (is-eq (some tx-sender) (nft-get-owner? time-nft token-id)) ERR_NOT_TOKEN_OWNER)
        (try! (nft-burn? time-nft token-id tx-sender))
        (map-delete token-metadata { token-id: token-id })
        (map-delete token-uris { token-id: token-id })
        (map-delete royalty-recipients { token-id: token-id })
        (ok true)
    )
)

(define-public (set-royalty (token-id uint) (recipient principal) (percentage uint))
    (begin
        (asserts! (is-eq (some tx-sender) (nft-get-owner? time-nft token-id)) ERR_NOT_TOKEN_OWNER)
        (asserts! (<= percentage u1000) ERR_INVALID_UNLOCK_HEIGHT)
        (ok (map-set royalty-recipients { token-id: token-id } { recipient: recipient, percentage: percentage }))
    )
)

(define-public (set-mint-price (new-price uint))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
        (ok (var-set mint-price new-price))
    )
)

(define-public (set-contract-paused (paused bool))
    (begin
        (asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_OWNER_ONLY)
        (ok (var-set contract-paused paused))
    )
)


;; additional read-only functions
(define-read-only (get-mint-price)
    (ok (var-get mint-price))
)

(define-read-only (get-royalty-info (token-id uint))
    (map-get? royalty-recipients { token-id: token-id })
)

(define-read-only (get-contract-paused)
    (ok (var-get contract-paused))
)

(define-read-only (get-tokens-by-creator (creator principal))
    (map-get? creator-tokens { creator: creator })
)

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

(define-read-only (token-exists (token-id uint))
    (is-some (nft-get-owner? time-nft token-id))
)

(define-read-only (get-token-info (token-id uint))
    (match (get-token-metadata token-id)
        metadata (ok {
            metadata: metadata,
            owner: (nft-get-owner? time-nft token-id),
            unlocked: (>= stacks-block-height (get unlock-height metadata)),
            blocks-remaining: (if (>= stacks-block-height (get unlock-height metadata)) 
                                u0 
                                (- (get unlock-height metadata) stacks-block-height))
        })
        ERR_NOT_FOUND
    )
)

Functions (22)

FunctionAccessArgs
transferpublictoken-id: uint, sender: principal, recipient: principal
set-token-uripublictoken-id: uint, uri: (optional (string-ascii 256
get-last-token-idread-only
get-token-uriread-onlytoken-id: uint
get-ownerread-onlytoken-id: uint
get-token-metadataread-onlytoken-id: uint
is-unlockedread-onlytoken-id: uint
get-unlocked-contentread-onlytoken-id: uint
get-unlock-heightread-onlytoken-id: uint
blocks-until-unlockread-onlytoken-id: uint
update-unlock-heightpublictoken-id: uint, new-unlock-height: uint
burnpublictoken-id: uint
set-royaltypublictoken-id: uint, recipient: principal, percentage: uint
set-mint-pricepublicnew-price: uint
set-contract-pausedpublicpaused: bool
get-mint-priceread-only
get-royalty-inforead-onlytoken-id: uint
get-contract-pausedread-only
get-tokens-by-creatorread-onlycreator: principal
get-total-supplyread-only
token-existsread-onlytoken-id: uint
get-token-inforead-onlytoken-id: uint