Source Code


(use-trait nft-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)
(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)

(define-constant CONTRACT-OWNER tx-sender)

(define-constant ERR-NOT-AUTHORIZED (err u200))
(define-constant ERR-ALREADY-LISTED (err u201))
(define-constant ERR-NOT-LISTED (err u202))
(define-constant ERR-NOT-OWNER (err u203))
(define-constant ERR-FT-NOT-WHITELISTED (err u204))
(define-constant ERR-INVALID-PRICE (err u205))
(define-constant ERR-CANNOT-BUY-OWN (err u206))
(define-constant ERR-PAUSED (err u207))
(define-constant ERR-WRONG-NFT (err u208))
(define-constant ERR-ALREADY-INITIALIZED (err u209))
(define-constant ERR-NOT-INITIALIZED (err u210))
(define-constant ERR-WRONG-FT (err u211))

(define-data-var contract-paused bool false)
(define-data-var royalty-percent uint u250)
(define-data-var royalty-recipient principal CONTRACT-OWNER)
(define-data-var platform-fee uint u250)
(define-data-var platform-recipient principal CONTRACT-OWNER)

(define-data-var allowed-nft (optional principal) none)

(define-data-var default-price uint u100000000000) 
(define-data-var default-seller principal 'SP3E8B51MF5E28BD82FM95VDSQ71VK4KFNZX7ZK2R) 
(define-data-var default-ft (optional principal) none) 

(define-map whitelisted-fts principal bool)

(define-map listings uint {
  seller: principal,
  ft-contract: principal,
  price: uint,
  listed-at: uint
})

(define-public (initialize (nft-contract principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (asserts! (is-none (var-get allowed-nft)) ERR-ALREADY-INITIALIZED)
    (var-set allowed-nft (some nft-contract))
    (print {event: "initialized", nft-contract: nft-contract})
    (ok true)))

(define-public (set-paused (paused bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set contract-paused paused)
    (print {event: "contract-paused", paused: paused})
    (ok true)))

(define-public (whitelist-ft (ft <ft-trait>) (whitelisted bool))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (map-set whitelisted-fts (contract-of ft) whitelisted)
    (print {event: "ft-whitelist-update", ft-contract: (contract-of ft), whitelisted: whitelisted})
    (ok true)))

(define-public (set-royalty-percent (percent uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (asserts! (<= percent u1000) ERR-NOT-AUTHORIZED)
    (var-set royalty-percent percent)
    (print {event: "royalty-percent-updated", percent: percent})
    (ok true)))

(define-public (set-royalty-recipient (recipient principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set royalty-recipient recipient)
    (print {event: "royalty-recipient-updated", recipient: recipient})
    (ok true)))

(define-public (set-platform-fee (fee uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (asserts! (<= fee u500) ERR-NOT-AUTHORIZED)
    (var-set platform-fee fee)
    (print {event: "platform-fee-updated", fee: fee})
    (ok true)))

(define-public (set-platform-recipient (recipient principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set platform-recipient recipient)
    (print {event: "platform-recipient-updated", recipient: recipient})
    (ok true)))

(define-public (set-default-price (price uint))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (asserts! (> price u0) ERR-INVALID-PRICE)
    (var-set default-price price)
    (print {event: "default-price-updated", price: price})
    (ok true)))

(define-public (set-default-seller (seller principal))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (var-set default-seller seller)
    (print {event: "default-seller-updated", seller: seller})
    (ok true)))

(define-public (set-default-ft (ft <ft-trait>))
  (begin
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (asserts! (is-ft-whitelisted (contract-of ft)) ERR-FT-NOT-WHITELISTED)
    (var-set default-ft (some (contract-of ft)))
    (print {event: "default-ft-updated", ft-contract: (contract-of ft)})
    (ok true)))

(define-private (check-nft-allowed (nft-contract <nft-trait>))
  (match (var-get allowed-nft)
    allowed (is-eq (contract-of nft-contract) allowed)
    false))

(define-public (list-nft (token-id uint) (nft-contract <nft-trait>) (ft-contract <ft-trait>) (price uint))
  (let (
    (ft-principal (contract-of ft-contract))
    (seller tx-sender)
  )
    (asserts! (is-some (var-get allowed-nft)) ERR-NOT-INITIALIZED)
    (asserts! (check-nft-allowed nft-contract) ERR-WRONG-NFT)
    (asserts! (not (var-get contract-paused)) ERR-PAUSED)
    (asserts! (> price u0) ERR-INVALID-PRICE)
    (asserts! (is-ft-whitelisted ft-principal) ERR-FT-NOT-WHITELISTED)
    (asserts! (is-none (map-get? listings token-id)) ERR-ALREADY-LISTED)

    (try! (contract-call? nft-contract transfer token-id seller current-contract))

    (map-set listings token-id {
      seller: seller,
      ft-contract: ft-principal,
      price: price,
      listed-at: stacks-block-height
    })

    (print {
      event: "nft-listed",
      token-id: token-id,
      seller: seller,
      ft-contract: ft-principal,
      price: price
    })
    (ok true)))

(define-public (update-price (token-id uint) (new-price uint))
  (let (
    (listing (unwrap! (map-get? listings token-id) ERR-NOT-LISTED))
  )
    (asserts! (not (var-get contract-paused)) ERR-PAUSED)
    (asserts! (is-eq (get seller listing) tx-sender) ERR-NOT-OWNER)
    (asserts! (> new-price u0) ERR-INVALID-PRICE)

    (map-set listings token-id (merge listing {price: new-price}))

    (print {event: "price-updated", token-id: token-id, new-price: new-price})
    (ok true)))

(define-public (update-listing-ft (token-id uint) (new-ft-contract <ft-trait>) (new-price uint))
  (let (
    (listing (unwrap! (map-get? listings token-id) ERR-NOT-LISTED))
    (new-ft-principal (contract-of new-ft-contract))
  )
    (asserts! (not (var-get contract-paused)) ERR-PAUSED)
    (asserts! (is-eq (get seller listing) tx-sender) ERR-NOT-OWNER)
    (asserts! (> new-price u0) ERR-INVALID-PRICE)
    (asserts! (is-ft-whitelisted new-ft-principal) ERR-FT-NOT-WHITELISTED)

    (map-set listings token-id (merge listing {
      ft-contract: new-ft-principal,
      price: new-price
    }))

    (print {
      event: "listing-updated",
      token-id: token-id,
      ft-contract: new-ft-principal,
      price: new-price
    })
    (ok true)))

(define-public (unlist-nft (token-id uint) (nft-contract <nft-trait>))
  (let (
    (listing (unwrap! (map-get? listings token-id) ERR-NOT-LISTED))
    (seller (get seller listing))
  )
    (asserts! (check-nft-allowed nft-contract) ERR-WRONG-NFT)
    (asserts! (is-eq seller tx-sender) ERR-NOT-OWNER)

    (try! (as-contract? ((with-nft (contract-of nft-contract) "*" (list token-id)))
      (try! (contract-call? nft-contract transfer token-id current-contract seller))))

    (map-delete listings token-id)

    (print {event: "nft-unlisted", token-id: token-id, seller: seller})
    (ok true)))

(define-public (buy-nft (token-id uint) (nft-contract <nft-trait>) (ft-contract <ft-trait>))
  (let (
    (buyer tx-sender)
    (listing (unwrap! (map-get? listings token-id) ERR-NOT-LISTED))
    (seller (get seller listing))
    (price (get price listing))
    (listing-ft (get ft-contract listing))
    (royalty-amount (/ (* price (var-get royalty-percent)) u10000))
    (platform-amount (/ (* price (var-get platform-fee)) u10000))
    (seller-amount (- price (+ royalty-amount platform-amount)))
  )
    (asserts! (check-nft-allowed nft-contract) ERR-WRONG-NFT)
    (asserts! (not (var-get contract-paused)) ERR-PAUSED)
    (asserts! (is-eq (contract-of ft-contract) listing-ft) ERR-WRONG-FT)
    (asserts! (is-ft-whitelisted listing-ft) ERR-FT-NOT-WHITELISTED)
    (asserts! (not (is-eq buyer seller)) ERR-CANNOT-BUY-OWN)

    (try! (contract-call? ft-contract transfer seller-amount buyer seller none))

    (if (> royalty-amount u0)
      (try! (contract-call? ft-contract transfer royalty-amount buyer (var-get royalty-recipient) none))
      true
    )

    (if (> platform-amount u0)
      (try! (contract-call? ft-contract transfer platform-amount buyer (var-get platform-recipient) none))
      true
    )

    (try! (as-contract? ((with-nft (contract-of nft-contract) "*" (list token-id)))
      (try! (contract-call? nft-contract transfer token-id current-contract buyer))))

    (map-delete listings token-id)

    (print {
      event: "nft-sold",
      token-id: token-id,
      seller: seller,
      buyer: buyer,
      price: price,
      ft-contract: listing-ft,
      royalty-paid: royalty-amount,
      platform-fee-paid: platform-amount
    })
    (ok true)))

(define-public (premint (token-id uint) (nft-contract <nft-trait>) (ft-contract <ft-trait>))
  (let (
    (buyer tx-sender)
    (seller (var-get default-seller))
    (price (var-get default-price))
    (expected-ft (unwrap! (var-get default-ft) ERR-NOT-INITIALIZED))
    (platform-amount (/ (* price u1000) u10000))
    (seller-amount (- price platform-amount))
  )
    (asserts! (check-nft-allowed nft-contract) ERR-WRONG-NFT)
    (asserts! (not (var-get contract-paused)) ERR-PAUSED)
    (asserts! (is-eq (contract-of ft-contract) expected-ft) ERR-WRONG-FT)
    (asserts! (is-ft-whitelisted expected-ft) ERR-FT-NOT-WHITELISTED)
    (asserts! (not (is-eq buyer seller)) ERR-CANNOT-BUY-OWN)
    
    (asserts! (is-none (map-get? listings token-id)) ERR-ALREADY-LISTED)

    (try! (contract-call? ft-contract transfer seller-amount buyer seller none))

    (if (> platform-amount u0)
      (try! (contract-call? ft-contract transfer platform-amount buyer (var-get platform-recipient) none))
      true
    )

    (try! (as-contract? ((with-nft (contract-of nft-contract) "*" (list token-id)))
      (try! (contract-call? nft-contract transfer token-id current-contract buyer))))

    (print {
      event: "premint-nft-sold",
      token-id: token-id,
      seller: seller,
      buyer: buyer,
      price: price,
      ft-contract: expected-ft,
      platform-fee-paid: platform-amount
    })
    (ok true)))

(define-public (admin-emergency-return (token-id uint) (nft-contract <nft-trait>))
  (let (
    (listing (unwrap! (map-get? listings token-id) ERR-NOT-LISTED))
    (seller (get seller listing))
  )
    (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
    (asserts! (check-nft-allowed nft-contract) ERR-WRONG-NFT)

    (try! (as-contract? ((with-nft (contract-of nft-contract) "*" (list token-id)))
      (try! (contract-call? nft-contract transfer token-id current-contract seller))))

    (map-delete listings token-id)

    (print {event: "admin-emergency-return", token-id: token-id, seller: seller})
    (ok true)))

(define-read-only (get-listing (token-id uint))
  (map-get? listings token-id))

(define-read-only (is-ft-whitelisted (ft-contract principal))
  (default-to false (map-get? whitelisted-fts ft-contract)))

(define-read-only (get-allowed-nft)
  (var-get allowed-nft))

(define-read-only (get-royalty-info)
  {
    percent: (var-get royalty-percent),
    recipient: (var-get royalty-recipient)
  })

(define-read-only (get-platform-info)
  {
    fee: (var-get platform-fee),
    recipient: (var-get platform-recipient)
  })

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

(define-read-only (is-initialized)
  (is-some (var-get allowed-nft)))

(define-read-only (get-default-listing-info)
  {
    price: (var-get default-price),
    seller: (var-get default-seller),
    ft-contract: (var-get default-ft)
  })

(map-set whitelisted-fts 'SP3E8B51MF5E28BD82FM95VDSQ71VK4KFNZX7ZK2R.frog-faktory true)
(var-set default-ft (some 'SP3E8B51MF5E28BD82FM95VDSQ71VK4KFNZX7ZK2R.frog-faktory))

Functions (26)

FunctionAccessArgs
initializepublicnft-contract: principal
set-pausedpublicpaused: bool
whitelist-ftpublicft: <ft-trait>, whitelisted: bool
set-royalty-percentpublicpercent: uint
set-royalty-recipientpublicrecipient: principal
set-platform-feepublicfee: uint
set-platform-recipientpublicrecipient: principal
set-default-pricepublicprice: uint
set-default-sellerpublicseller: principal
set-default-ftpublicft: <ft-trait>
check-nft-allowedprivatenft-contract: <nft-trait>
list-nftpublictoken-id: uint, nft-contract: <nft-trait>, ft-contract: <ft-trait>, price: uint
update-pricepublictoken-id: uint, new-price: uint
update-listing-ftpublictoken-id: uint, new-ft-contract: <ft-trait>, new-price: uint
unlist-nftpublictoken-id: uint, nft-contract: <nft-trait>
buy-nftpublictoken-id: uint, nft-contract: <nft-trait>, ft-contract: <ft-trait>
premintpublictoken-id: uint, nft-contract: <nft-trait>, ft-contract: <ft-trait>
admin-emergency-returnpublictoken-id: uint, nft-contract: <nft-trait>
get-listingread-onlytoken-id: uint
is-ft-whitelistedread-onlyft-contract: principal
get-allowed-nftread-only
get-royalty-inforead-only
get-platform-inforead-only
is-pausedread-only
is-initializedread-only
get-default-listing-inforead-only