Source Code

;; dcards-nft
;; NFT contract for stacks cards
;; https://cards.layerc.xyz

;; contract name
(define-non-fungible-token dcards-nft uint)

;; initializers

(define-data-var mint-price uint u3000000)
(define-data-var current-mint-price uint u3000000)

;; Store the last issued token ID
(define-data-var last-id uint u0)

;; postman-wallet can transfer tokens once
(define-data-var postman-wallet principal tx-sender) ;; deployer

;; this has to be the exact length of the string
(define-data-var meta-url (string-ascii 49) "https://cards.layerc.xyz/api/tokens/cards/v1/{id}")

;; data maps and vars

;; map tokenId: seed
(define-map seeds uint (buff 128))

(define-map claimed uint bool)

;; map owner/operator/tokenId: approved
(define-map approvals {owner: principal, operator: principal, id: uint} bool)
(define-map approvals-all {owner: principal, operator: principal} bool)

;; public functions

;; Mint new NFT
(define-public (mint (new-owner principal) (seed (buff 128)))
  (begin
    (var-set current-mint-price (var-get mint-price))
    (mint-with-seeds {address: new-owner, seed: seed})))

(define-private (mint-with-seeds (details {address: principal, seed: (buff 128)}))
  (let ((next-id (+ u1 (var-get last-id)))
    (new-owner (get address details))
    (seed (get seed details)))
    ;; pay the mint price
    (try! (match (stx-transfer? (var-get current-mint-price) tx-sender (var-get postman-wallet) )
      success (ok success)
      error (err (* error u10))))
    ;; mint the NFT
    (try! (nft-mint? dcards-nft next-id new-owner))
    (var-set last-id next-id)
    (map-insert seeds next-id seed) ;; ensures that token-id is unique
    (print {action: "mint", minted: next-id, seed: seed})
    (ok next-id)))

(define-read-only (check-err (result (response uint uint)) (previous-result (response (list 500 uint) {ids: (list 500 uint), error: uint})))
  (match previous-result
    ids (match result
      id (ok (unwrap! (as-max-len? (append ids id) u500) (err {ids: (list), error: u999})))
      error (err {ids: ids, error: error}))
    error (err error)))

;; mint many nfts
(define-public (mint-many (details (list 500 {address: principal, seed: (buff 128)})))
  (let ((price (var-get mint-price))
      (length (len details)))
    (var-set current-mint-price (if (>= length u10) (if (>= length u50) (if (>= length u100)
      (* (/ price u5) u3)
      (* (/ price u3) u2))
      (* (/ price u6) u5))
      price))
    (fold check-err (map mint-with-seeds details) (ok (list)))))

;; get seed for tokenId
(define-read-only (get-seed (token-id uint))
  (map-get? seeds token-id))

;; SIP009: Transfer token to a specified principal
;; the token-id has to be owned by the sender? validated inside nft-transfer
(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    (asserts! (or
                (is-eq tx-sender sender)
                (is-approved-with-owner token-id contract-caller (unwrap! (nft-get-owner? dcards-nft token-id) (err u404)))
                (handle-postman-transfer token-id))
        (err u403))
      ;; nft-transfer? fails if sender is not owner
      (nft-transfer? dcards-nft token-id sender recipient)))

;; Transfer token to a specified principal with a memo
(define-public (transfer-memo (id uint) (sender principal) (recipient principal) (memo (buff 34)))
    (begin
      (try! (transfer id sender recipient))
      (print memo)
      (ok true)))

;; Allow clients to check caller permission without changing claimed map
(define-read-only (can-transfer (token-id uint) (sender principal) (recipient principal))
  (or (is-eq tx-sender sender)
      (is-approved-with-owner token-id contract-caller (unwrap! (nft-get-owner? dcards-nft token-id) false))
      (and
        (is-eq contract-caller (var-get postman-wallet))
        (not (default-to false (map-get? claimed token-id))))))

;; check postman address and only allow transfer once
(define-private (handle-postman-transfer (token-id uint))
  (and (is-eq contract-caller (var-get postman-wallet))
    ;; map-insert returns false if entry exists
    (map-insert claimed token-id true)))

;;
;; operable functions

(define-private (is-approved-with-owner (token-id uint) (operator principal) (owner principal))
  (or
    (is-eq operator owner)
    (default-to
      (default-to
        false
        (map-get? approvals-all {owner: owner, operator: operator}))
          (map-get? approvals {owner: owner, operator: operator, id:  token-id}))))

(define-read-only (is-approved ( token-id uint) (operator principal))
  (let ((owner (unwrap! (nft-get-owner? dcards-nft  token-id) (err u404))))
    (ok (is-approved-with-owner token-id operator owner))))

(define-public (set-approved ( token-id uint) (operator principal) (approved bool))
  (ok (map-set approvals {owner: contract-caller, operator: operator, id: token-id} approved)))

(define-public (set-approved-all (operator principal) (approved bool))
  (ok (map-set approvals-all {owner: contract-caller, operator: operator} approved)))


;; SIP009: Get the owner of the specified token ID
(define-read-only (get-owner (token-id uint))
  (ok (nft-get-owner? dcards-nft token-id)))

;; SIP009: Get the last token ID
(define-read-only (get-last-token-id)
  (ok (var-get last-id)))

(define-read-only (get-info (token-id uint))
  (ok {
    owner: (nft-get-owner? dcards-nft token-id),
    seed: (get-seed token-id),
    approved: (is-approved token-id contract-caller),
    claimed: (map-get? claimed token-id),
    last-id: (var-get last-id),
    mint-price: (var-get mint-price)
  }))


(define-read-only (get-token-uri (token-id uint))
  (ok (some (var-get meta-url) )))

(define-read-only (get-mint-price)
  (var-get mint-price))

(define-public (set-mint-price (price uint))
  (begin
    (asserts! (is-eq tx-sender (var-get postman-wallet)) (err u403))
    (ok (var-set mint-price price))))

;; check status on last mint
(define-read-only (mint-check)
  (print {evt: "mint-check", last: (var-get last-id), owner: (get-owner (var-get last-id)), amount: (var-get mint-price)})
)

Functions (18)

FunctionAccessArgs
mintpublicnew-owner: principal, seed: (buff 128
check-errread-onlyresult: (response uint uint
get-seedread-onlytoken-id: uint
transferpublictoken-id: uint, sender: principal, recipient: principal
transfer-memopublicid: uint, sender: principal, recipient: principal, memo: (buff 34
can-transferread-onlytoken-id: uint, sender: principal, recipient: principal
handle-postman-transferprivatetoken-id: uint
is-approved-with-ownerprivatetoken-id: uint, operator: principal, owner: principal
is-approvedread-onlyoperator: principal
set-approvedpublicoperator: principal, approved: bool
set-approved-allpublicoperator: principal, approved: bool
get-ownerread-onlytoken-id: uint
get-last-token-idread-only
get-token-uriread-onlytoken-id: uint
get-mint-priceread-only
set-mint-pricepublicprice: uint
mint-checkread-only
get-inforead-onlytoken-id: uint