Source Code

;; StacksMint Marketplace Contract
;; Buy/sell NFTs with creator fees on each sale
;; Creator Fee: 0.01 STX per sale

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-NOT-FOUND (err u101))
(define-constant ERR-ALREADY-LISTED (err u102))
(define-constant ERR-NOT-TOKEN-OWNER (err u103))
(define-constant ERR-INVALID-PRICE (err u104))
(define-constant ERR-LISTING-NOT-FOUND (err u105))
(define-constant ERR-WRONG-PRICE (err u106))
(define-constant ERR-CANNOT-BUY-OWN (err u107))
(define-constant ERR-TRANSFER-FAILED (err u108))
(define-constant ERR-INSUFFICIENT-FUNDS (err u109))
(define-constant CREATOR-FEE u10000) ;; 0.01 STX in microSTX

;; Data Variables
(define-data-var total-listings uint u0)
(define-data-var total-sales uint u0)
(define-data-var total-volume uint u0)

;; Data Maps
(define-map listings uint {
  seller: principal,
  nft-contract: principal,
  token-id: uint,
  price: uint,
  listed-at: uint,
  is-active: bool
})

(define-map nft-listing { nft-contract: principal, token-id: uint } uint) ;; maps to listing-id

;; Read-only functions

;; Get creator fee
(define-read-only (get-creator-fee)
  CREATOR-FEE
)

;; Get total listings count
(define-read-only (get-total-listings)
  (var-get total-listings)
)

;; Get total sales count
(define-read-only (get-total-sales)
  (var-get total-sales)
)

;; Get total trading volume
(define-read-only (get-total-volume)
  (var-get total-volume)
)

;; Get listing by ID
(define-read-only (get-listing (listing-id uint))
  (map-get? listings listing-id)
)

;; Get listing ID for NFT
(define-read-only (get-nft-listing-id (nft-contract principal) (token-id uint))
  (map-get? nft-listing { nft-contract: nft-contract, token-id: token-id })
)

;; Get listing for NFT
(define-read-only (get-nft-listing (nft-contract principal) (token-id uint))
  (match (get-nft-listing-id nft-contract token-id)
    listing-id (map-get? listings listing-id)
    none
  )
)

;; Check if NFT is listed
(define-read-only (is-listed (nft-contract principal) (token-id uint))
  (match (get-nft-listing nft-contract token-id)
    listing (get is-active listing)
    false
  )
)

;; Get listing price
(define-read-only (get-listing-price (nft-contract principal) (token-id uint))
  (match (get-nft-listing nft-contract token-id)
    listing (if (get is-active listing)
      (some (get price listing))
      none
    )
    none
  )
)

;; Public functions

;; List NFT for sale
(define-public (list-nft (nft-contract principal) (token-id uint) (price uint))
  (let
    (
      (listing-id (+ (var-get total-listings) u1))
    )
    ;; Validate price
    (asserts! (> price u0) ERR-INVALID-PRICE)
    ;; Check not already listed
    (asserts! (not (is-listed nft-contract token-id)) ERR-ALREADY-LISTED)
    ;; Create listing
    (map-set listings listing-id {
      seller: tx-sender,
      nft-contract: nft-contract,
      token-id: token-id,
      price: price,
      listed-at: block-height,
      is-active: true
    })
    ;; Map NFT to listing
    (map-set nft-listing { nft-contract: nft-contract, token-id: token-id } listing-id)
    ;; Update total listings
    (var-set total-listings listing-id)
    (ok listing-id)
  )
)

;; Update listing price
(define-public (update-listing (nft-contract principal) (token-id uint) (new-price uint))
  (match (get-nft-listing-id nft-contract token-id)
    listing-id (match (map-get? listings listing-id)
      listing (begin
        (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED)
        (asserts! (get is-active listing) ERR-LISTING-NOT-FOUND)
        (asserts! (> new-price u0) ERR-INVALID-PRICE)
        (map-set listings listing-id 
          (merge listing { price: new-price })
        )
        (ok true)
      )
      ERR-LISTING-NOT-FOUND
    )
    ERR-LISTING-NOT-FOUND
  )
)

;; Cancel listing
(define-public (cancel-listing (nft-contract principal) (token-id uint))
  (match (get-nft-listing-id nft-contract token-id)
    listing-id (match (map-get? listings listing-id)
      listing (begin
        (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-AUTHORIZED)
        (asserts! (get is-active listing) ERR-LISTING-NOT-FOUND)
        (map-set listings listing-id 
          (merge listing { is-active: false })
        )
        (map-delete nft-listing { nft-contract: nft-contract, token-id: token-id })
        (ok true)
      )
      ERR-LISTING-NOT-FOUND
    )
    ERR-LISTING-NOT-FOUND
  )
)

;; Buy listed NFT (pays seller + creator fee)
(define-public (buy-nft (nft-contract principal) (token-id uint) (expected-price uint))
  (match (get-nft-listing-id nft-contract token-id)
    listing-id (match (map-get? listings listing-id)
      listing (let
        (
          (seller (get seller listing))
          (price (get price listing))
          (total-cost (+ price CREATOR-FEE))
        )
        ;; Validations
        (asserts! (get is-active listing) ERR-LISTING-NOT-FOUND)
        (asserts! (is-eq price expected-price) ERR-WRONG-PRICE)
        (asserts! (not (is-eq tx-sender seller)) ERR-CANNOT-BUY-OWN)
        ;; Pay seller
        (try! (stx-transfer? price tx-sender seller))
        ;; Pay creator fee
        (try! (stx-transfer? CREATOR-FEE tx-sender CONTRACT-OWNER))
        ;; Update listing to inactive
        (map-set listings listing-id 
          (merge listing { is-active: false })
        )
        ;; Remove NFT listing mapping
        (map-delete nft-listing { nft-contract: nft-contract, token-id: token-id })
        ;; Update stats
        (var-set total-sales (+ (var-get total-sales) u1))
        (var-set total-volume (+ (var-get total-volume) price))
        (ok { listing-id: listing-id, price: price, fee: CREATOR-FEE })
      )
      ERR-LISTING-NOT-FOUND
    )
    ERR-LISTING-NOT-FOUND
  )
)

;; Buy NFT with integrated transfer (for StacksMint NFTs)
;; Note: This requires the seller to have approved the marketplace contract
(define-public (buy-stacksmint-nft (token-id uint) (expected-price uint))
  (let
    (
      (nft-contract (as-contract tx-sender))
    )
    (buy-nft nft-contract token-id expected-price)
  )
)

;; Admin function to pause a listing (contract owner only - for emergencies)
(define-public (admin-pause-listing (listing-id uint))
  (match (map-get? listings listing-id)
    listing (begin
      (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
      (map-set listings listing-id 
        (merge listing { is-active: false })
      )
      (map-delete nft-listing { 
        nft-contract: (get nft-contract listing), 
        token-id: (get token-id listing) 
      })
      (ok true)
    )
    ERR-LISTING-NOT-FOUND
  )
)

Functions (15)

FunctionAccessArgs
get-creator-feeread-only
get-total-listingsread-only
get-total-salesread-only
get-total-volumeread-only
get-listingread-onlylisting-id: uint
get-nft-listing-idread-onlynft-contract: principal, token-id: uint
get-nft-listingread-onlynft-contract: principal, token-id: uint
is-listedread-onlynft-contract: principal, token-id: uint
get-listing-priceread-onlynft-contract: principal, token-id: uint
list-nftpublicnft-contract: principal, token-id: uint, price: uint
update-listingpublicnft-contract: principal, token-id: uint, new-price: uint
cancel-listingpublicnft-contract: principal, token-id: uint
buy-nftpublicnft-contract: principal, token-id: uint, expected-price: uint
buy-stacksmint-nftpublictoken-id: uint, expected-price: uint
admin-pause-listingpubliclisting-id: uint