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 (19)

FunctionAccessArgs
get-total-supplyread-only
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
burnpublictoken-id: 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
token-existsread-onlytoken-id: uint
get-token-inforead-onlytoken-id: uint