Source Code

;; Turtles NFT by Bitcoin Birds

(impl-trait .sip009-nft-trait.sip009-nft-trait)
(define-non-fungible-token turtles uint)

;; Errors
(define-constant ERR-SOLD-OUT (err u100))
(define-constant ERR-MINT-NOT-LIVE (err u101))
(define-constant ERR-NOT-AUTHORIZED (err u102))
(define-constant ERR-NOT-BIRD-OWNER (err u103))
(define-constant ERR-PRE-MINT-NOT-OVER (err u104))
(define-constant ERR-BIRD-ALREADY-USED (err u105))
(define-constant ERR-MINT-LIMIT-REACHED (err u106))
(define-constant ERR-PRE-MINT-NOT-STARTED (err u107))
(define-constant ERR-PRE-MINT-ALREADY-STARTED (err u108))
(define-constant ERR-TRANSFER-NOT-AUTHORIZED (err u403))

;; Constants
(define-constant PUBLIC-MINT-LIMIT u1)
(define-constant COLLECTION-LIMIT u800)

;; Mint fees (50 STX)
(define-constant MINT_FEE_1 u15000000) ;; 30% - Creator 1
(define-constant MINT_FEE_2 u10000000) ;; 20% - Creator 2
(define-constant MINT_FEE_3 u10000000) ;; 20% - Creator 3
(define-constant MINT_FEE_4 u10000000) ;; 20% - Marketplace commission
(define-constant MINT_FEE_5 u5000000)  ;; 10% - Turtle charity

;; Variables
(define-data-var last-id uint u0)
(define-data-var pre-mint-expiration uint u0)
(define-data-var pre-mint-initiated bool false)
(define-data-var public-mint-initiated bool false)
(define-data-var contract-owner principal tx-sender)
(define-data-var token-base-uri (string-ascii 128) "https://www.blockgallery.com/api/v1/collection/turtles/metadata/")

;; Withdraw wallets
(define-data-var withdraw-wallet_1 principal 'SP2K9XEKEG7BE5BTYWZDAXJ8QAZBJ2TQZJJY3MV90) ;; Creator 1
(define-data-var withdraw-wallet_2 principal 'SP2E1XNE523ZAWHW899HAAJ793PDGAJJZSGC3VEA8) ;; Creator 2
(define-data-var withdraw-wallet_3 principal 'SP1TFWX5VEYDEJ4RW0WQAKQTJPWBP2E305WW9V6P0) ;; Creator 3
(define-data-var withdraw-wallet_4 principal 'SPQB6S4QTWME28PB36Z6Z6GQEM95FYY08N3GAE75)  ;; Marketplace commission
(define-data-var withdraw-wallet_5 principal 'SP2MT2QCH5HDY4JZWJHQR5DX16VQ3CQZH1AQ9V14K)  ;; Turtle charity

;; Maps
(define-map birds-used uint principal)
(define-map withdraw-wallets principal uint)
(define-map principal-mint-count principal uint)

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

(define-read-only (is-pre-mint-initiated)
  (ok (var-get pre-mint-initiated))
)

(define-read-only (is-public-mint-initiated)
  (ok (var-get public-mint-initiated))
)

(define-read-only (get-pre-mint-expiration)
  (ok (var-get pre-mint-expiration))
)

(define-read-only (check-bird-used (bird-id uint))
  (ok (is-some (map-get? birds-used bird-id)))
)

(define-read-only (get-principal-mint-count (principal principal))
  (ok (default-to u0 (map-get? principal-mint-count principal)))
)

(define-public (set-withdraw-wallet (wallet-id uint) (principal principal))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)
    (and (is-eq wallet-id u1) (var-set withdraw-wallet_1 principal))
    (and (is-eq wallet-id u2) (var-set withdraw-wallet_2 principal))
    (and (is-eq wallet-id u3) (var-set withdraw-wallet_3 principal))
    (and (is-eq wallet-id u4) (var-set withdraw-wallet_4 principal))
    (and (is-eq wallet-id u5) (var-set withdraw-wallet_5 principal))
    (ok true)
  )
)

(define-public (initiate-pre-mint)
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)
    (asserts! (not (var-get pre-mint-initiated)) ERR-PRE-MINT-ALREADY-STARTED)
    (var-set pre-mint-expiration (+ block-height u288)) ;; ~48 hrs
    (ok (var-set pre-mint-initiated true))
  )
)

(define-public (initiate-public-mint)
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)
    (asserts! (var-get pre-mint-initiated) ERR-PRE-MINT-NOT-STARTED)
    (asserts! (>= block-height (var-get pre-mint-expiration)) ERR-PRE-MINT-NOT-OVER)
    (ok (var-set public-mint-initiated true))
  )
)

(define-public (pre-mint (bird-id uint))
  (begin
    (asserts! (and (var-get pre-mint-initiated) (< block-height (var-get pre-mint-expiration))) ERR-MINT-NOT-LIVE)
    (asserts! (is-none (map-get? birds-used bird-id)) ERR-BIRD-ALREADY-USED)
    (asserts! (is-eq (contract-call? 'SPJW1XE278YMCEYMXB8ZFGJMH8ZVAAEDP2S2PJYG.bitcoin-birds get-owner bird-id) (ok (some tx-sender))) ERR-NOT-BIRD-OWNER)
    (map-set birds-used bird-id tx-sender)
    (ok (try! (mint bird-id)))
  )
)

(define-public (public-mint (bird-id uint))
  (let ((mint-count (default-to u0 (map-get? principal-mint-count tx-sender))))
    (asserts! (var-get public-mint-initiated) ERR-MINT-NOT-LIVE)
    (asserts! (< mint-count PUBLIC-MINT-LIMIT) ERR-MINT-LIMIT-REACHED)
    (map-set principal-mint-count tx-sender (+ u1 mint-count))
    (ok (try! (mint bird-id)))
  )
)

(define-private (mint (bird-id uint))
  (let (
    (sender tx-sender)
    (next-id (+ u1 (var-get last-id)))
    (wallet_1 (var-get withdraw-wallet_1))
    (wallet_2 (var-get withdraw-wallet_2))
    (wallet_3 (var-get withdraw-wallet_3))
    (wallet_4 (var-get withdraw-wallet_4))
    (wallet_5 (var-get withdraw-wallet_5))
  )
    (asserts! (<= next-id COLLECTION-LIMIT) ERR-SOLD-OUT)
    (and (not (is-eq sender wallet_1)) (try! (stx-transfer? MINT_FEE_1 sender wallet_1)))
    (and (not (is-eq sender wallet_2)) (try! (stx-transfer? MINT_FEE_2 sender wallet_2)))
    (and (not (is-eq sender wallet_3)) (try! (stx-transfer? MINT_FEE_3 sender wallet_3)))
    (and (not (is-eq sender wallet_4)) (try! (stx-transfer? MINT_FEE_4 sender wallet_4)))
    (and (not (is-eq sender wallet_5)) (try! (stx-transfer? MINT_FEE_5 sender wallet_5)))
    (try! (nft-mint? turtles next-id sender))
    (ok (var-set last-id next-id))
  )
)

(define-public (set-token-base-uri (new-uri (string-ascii 128)))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)
    (ok (var-set token-base-uri new-uri))
  )
)

(define-public (set-contract-owner (new-owner principal))
  (begin
    (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)
    (ok (var-set contract-owner new-owner))
  )
)

;; SIP009
(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is-eq tx-sender sender) ERR-TRANSFER-NOT-AUTHORIZED)
    (nft-transfer? turtles token-id sender recipient)
  )
)

;; SIP009
(define-read-only (get-owner (token-id uint))
  (ok (nft-get-owner? turtles token-id))
)

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

;; SIP009
(define-read-only (get-token-uri (token-id uint))
  (ok (match (token-id-to-string-ascii? token-id)
    id-string (some (concat (concat (var-get token-base-uri) id-string) ".json"))
    none
  ))
)

;; Dynamic token URI
;; Credit: SP3BS8RXKKAAM7HA76SFDKG4Q2YSG0X8K5K44XNDV.jelly-nft
(define-constant digits-to-string (list "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"))

(define-private (extract-digit (n uint) (digit uint))
  (mod (/ n (pow u10 digit)) u10)
)

(define-private (token-id-to-string-ascii? (n uint))
  (begin
    (asserts! (<= n COLLECTION-LIMIT) none)
    (some
      (concat (unwrap-panic (element-at digits-to-string (extract-digit n u2)))
      (concat
        (unwrap-panic (element-at digits-to-string (extract-digit n u1)))
        (unwrap-panic (element-at digits-to-string (mod n u10)))
      ))
    )
  )
)

Functions (20)

FunctionAccessArgs
get-contract-ownerread-only
is-pre-mint-initiatedread-only
is-public-mint-initiatedread-only
get-pre-mint-expirationread-only
check-bird-usedread-onlybird-id: uint
get-principal-mint-countread-onlyprincipal: principal
set-withdraw-walletpublicwallet-id: uint, principal: principal
initiate-pre-mintpublic
initiate-public-mintpublic
pre-mintpublicbird-id: uint
public-mintpublicbird-id: uint
mintprivatebird-id: uint
set-token-base-uripublicnew-uri: (string-ascii 128
set-contract-ownerpublicnew-owner: principal
transferpublictoken-id: uint, sender: principal, recipient: principal
get-ownerread-onlytoken-id: uint
get-last-token-idread-only
get-token-uriread-onlytoken-id: uint
extract-digitprivaten: uint, digit: uint
token-id-to-string-ascii?privaten: uint