Source Code

;; Gaga Finance Marketplace Core
;; Central orchestration contract for fixed-price listings
;; Coordinates with Escrow, Royalty Engine, and Auction modules

;; ============================================
;; TRAITS
;; ============================================
(use-trait nft-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

;; ============================================
;; CONSTANTS - Error Codes
;; ============================================
(define-constant ERR-NOT-AUTHORIZED (err u500))
(define-constant ERR-NOT-FOUND (err u501))
(define-constant ERR-LISTING-EXISTS (err u502))
(define-constant ERR-LISTING-NOT-ACTIVE (err u503))
(define-constant ERR-INSUFFICIENT-FUNDS (err u504))
(define-constant ERR-TRANSFER-FAILED (err u505))
(define-constant ERR-CONTRACT-PAUSED (err u506))
(define-constant ERR-INVALID-PRICE (err u507))
(define-constant ERR-CANNOT-BUY-OWN (err u508))
(define-constant ERR-NOT-TOKEN-OWNER (err u509))
(define-constant ERR-NOT-SELLER (err u510))
(define-constant ERR-NFT-NOT-APPROVED (err u511))

;; ============================================
;; CONFIGURATION
;; ============================================
(define-constant CONTRACT-OWNER tx-sender)

;; Protocol fee: 2.5% (250 basis points)
(define-constant DEFAULT-PROTOCOL-FEE-BPS u250)

;; Basis points denominator
(define-constant BPS-DENOMINATOR u10000)

;; ============================================
;; DATA VARIABLES
;; ============================================
(define-data-var is-paused bool false)
(define-data-var next-listing-id uint u1)
(define-data-var protocol-fee-bps uint DEFAULT-PROTOCOL-FEE-BPS)
(define-data-var fee-recipient principal CONTRACT-OWNER)

;; Module references
(define-data-var escrow-contract principal CONTRACT-OWNER)
(define-data-var royalty-contract principal CONTRACT-OWNER)
(define-data-var auction-contract principal CONTRACT-OWNER)

;; Stats
(define-data-var total-listings uint u0)
(define-data-var total-sales uint u0)
(define-data-var total-volume uint u0)
(define-data-var total-fees-collected uint u0)

;; ============================================
;; DATA MAPS
;; ============================================

;; Fixed-price listings
(define-map listings uint {
  seller: principal,
  nft-contract: principal,
  token-id: uint,
  price: uint,
  created-at-block: uint,
  is-active: bool
})

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

;; Seller's active listings count
(define-map seller-listing-count principal uint)

;; ============================================
;; AUTHORIZATION
;; ============================================

(define-private (is-contract-owner)
  (is-eq tx-sender CONTRACT-OWNER)
)

(define-private (assert-not-paused)
  (ok (asserts! (not (var-get is-paused)) ERR-CONTRACT-PAUSED))
)

;; ============================================
;; READ-ONLY FUNCTIONS
;; ============================================

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

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

;; Check if NFT is listed
(define-read-only (is-listed (nft-contract principal) (token-id uint))
  (match (map-get? nft-to-listing { nft-contract: nft-contract, token-id: token-id })
    listing-id (match (map-get? listings listing-id)
      listing (get is-active listing)
      false
    )
    false
  )
)

;; Get next listing ID
(define-read-only (get-next-listing-id)
  (ok (var-get next-listing-id))
)

;; Get protocol fee in basis points
(define-read-only (get-protocol-fee-bps)
  (ok (var-get protocol-fee-bps))
)

;; Get fee recipient
(define-read-only (get-fee-recipient)
  (ok (var-get fee-recipient))
)

;; Check pause status
(define-read-only (get-paused-status)
  (ok (var-get is-paused))
)

;; Get marketplace stats
(define-read-only (get-stats)
  (ok {
    total-listings: (var-get total-listings),
    total-sales: (var-get total-sales),
    total-volume: (var-get total-volume),
    total-fees: (var-get total-fees-collected)
  })
)

;; Get seller's listing count
(define-read-only (get-seller-listing-count (seller principal))
  (ok (default-to u0 (map-get? seller-listing-count seller)))
)

;; Calculate purchase costs
(define-read-only (get-purchase-breakdown (listing-id uint))
  (match (map-get? listings listing-id)
    listing (let (
      (price (get price listing))
      (protocol-fee (/ (* price (var-get protocol-fee-bps)) BPS-DENOMINATOR))
      (seller-proceeds (- price protocol-fee))
    )
      (ok {
        price: price,
        protocol-fee: protocol-fee,
        seller-proceeds: seller-proceeds
      })
    )
    ERR-NOT-FOUND
  )
)

;; ============================================
;; PUBLIC FUNCTIONS - LISTING MANAGEMENT
;; ============================================

;; Create a fixed-price listing
(define-public (create-listing 
  (nft-contract <nft-trait>)
  (token-id uint)
  (price uint)
)
  (let (
    (listing-id (var-get next-listing-id))
    (nft-principal (contract-of nft-contract))
  )
    (try! (assert-not-paused))
    
    ;; Validate price
    (asserts! (> price u0) ERR-INVALID-PRICE)
    
    ;; Check NFT not already listed
    (asserts! (not (is-listed nft-principal token-id)) ERR-LISTING-EXISTS)
    
    ;; Verify sender owns the NFT
    (let (
      (owner-result (try! (contract-call? nft-contract get-owner token-id)))
    )
      (asserts! (is-eq owner-result (some tx-sender)) ERR-NOT-TOKEN-OWNER)
    )
    
    ;; Create listing record
    (map-set listings listing-id {
      seller: tx-sender,
      nft-contract: nft-principal,
      token-id: token-id,
      price: price,
      created-at-block: stacks-block-height,
      is-active: true
    })
    
    ;; Map NFT to listing
    (map-set nft-to-listing { nft-contract: nft-principal, token-id: token-id } listing-id)
    
    ;; Update seller count
    (map-set seller-listing-count tx-sender 
      (+ (default-to u0 (map-get? seller-listing-count tx-sender)) u1)
    )
    
    ;; Increment counters
    (var-set next-listing-id (+ listing-id u1))
    (var-set total-listings (+ (var-get total-listings) u1))
    
    (print {
      event: "listing-created",
      listing-id: listing-id,
      seller: tx-sender,
      nft-contract: nft-principal,
      token-id: token-id,
      price: price
    })
    
    (ok listing-id)
  )
)

;; Update listing price
(define-public (update-listing-price (listing-id uint) (new-price uint))
  (let (
    (listing (unwrap! (map-get? listings listing-id) ERR-NOT-FOUND))
  )
    (try! (assert-not-paused))
    
    ;; Only seller can update
    (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-SELLER)
    
    ;; Must be active
    (asserts! (get is-active listing) ERR-LISTING-NOT-ACTIVE)
    
    ;; Validate new price
    (asserts! (> new-price u0) ERR-INVALID-PRICE)
    
    ;; Update price
    (map-set listings listing-id (merge listing { price: new-price }))
    
    (print {
      event: "listing-price-updated",
      listing-id: listing-id,
      old-price: (get price listing),
      new-price: new-price
    })
    
    (ok true)
  )
)

;; Cancel a listing
(define-public (cancel-listing (listing-id uint))
  (let (
    (listing (unwrap! (map-get? listings listing-id) ERR-NOT-FOUND))
  )
    ;; Only seller can cancel
    (asserts! (is-eq tx-sender (get seller listing)) ERR-NOT-SELLER)
    
    ;; Must be active
    (asserts! (get is-active listing) ERR-LISTING-NOT-ACTIVE)
    
    ;; Deactivate listing
    (map-set listings listing-id (merge listing { is-active: false }))
    
    ;; Remove NFT mapping
    (map-delete nft-to-listing { 
      nft-contract: (get nft-contract listing), 
      token-id: (get token-id listing) 
    })
    
    ;; Decrement seller count
    (let (
      (current-count (default-to u0 (map-get? seller-listing-count tx-sender)))
    )
      (if (> current-count u0)
        (map-set seller-listing-count tx-sender (- current-count u1))
        true
      )
    )
    
    (print {
      event: "listing-cancelled",
      listing-id: listing-id,
      seller: tx-sender
    })
    
    (ok true)
  )
)

;; ============================================
;; PUBLIC FUNCTIONS - PURCHASES
;; ============================================

;; Buy a listed NFT
(define-public (buy-listing 
  (listing-id uint)
  (nft-contract <nft-trait>)
)
  (let (
    (listing (unwrap! (map-get? listings listing-id) ERR-NOT-FOUND))
    (nft-principal (contract-of nft-contract))
  )
    (try! (assert-not-paused))
    
    ;; Validate NFT contract matches
    (asserts! (is-eq nft-principal (get nft-contract listing)) ERR-NOT-FOUND)
    
    ;; Must be active
    (asserts! (get is-active listing) ERR-LISTING-NOT-ACTIVE)
    
    ;; Cannot buy own listing
    (asserts! (not (is-eq tx-sender (get seller listing))) ERR-CANNOT-BUY-OWN)
    
    (let (
      (price (get price listing))
      (seller (get seller listing))
      (token-id (get token-id listing))
      (protocol-fee (/ (* price (var-get protocol-fee-bps)) BPS-DENOMINATOR))
      (seller-proceeds (- price protocol-fee))
    )
      ;; Transfer STX from buyer to seller
      (try! (stx-transfer? seller-proceeds tx-sender seller))
      
      ;; Transfer protocol fee to fee recipient
      (if (> protocol-fee u0)
        (try! (stx-transfer? protocol-fee tx-sender (var-get fee-recipient)))
        true
      )
      
      ;; Transfer NFT from seller to buyer
      (try! (contract-call? nft-contract transfer token-id seller tx-sender))
      
      ;; Deactivate listing
      (map-set listings listing-id (merge listing { is-active: false }))
      
      ;; Remove NFT mapping
      (map-delete nft-to-listing { nft-contract: nft-principal, token-id: token-id })
      
      ;; Update stats
      (var-set total-sales (+ (var-get total-sales) u1))
      (var-set total-volume (+ (var-get total-volume) price))
      (var-set total-fees-collected (+ (var-get total-fees-collected) protocol-fee))
      
      ;; Decrement seller's listing count
      (let (
        (current-count (default-to u0 (map-get? seller-listing-count seller)))
      )
        (if (> current-count u0)
          (map-set seller-listing-count seller (- current-count u1))
          true
        )
      )
      
      (print {
        event: "listing-sold",
        listing-id: listing-id,
        seller: seller,
        buyer: tx-sender,
        nft-contract: nft-principal,
        token-id: token-id,
        price: price,
        protocol-fee: protocol-fee,
        seller-proceeds: seller-proceeds
      })
      
      (ok true)
    )
  )
)

;; ============================================
;; ADMIN FUNCTIONS
;; ============================================

;; Pause contract
(define-public (set-paused (paused bool))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    (var-set is-paused paused)
    (print { event: "pause-status-changed", paused: paused })
    (ok true)
  )
)

;; Set protocol fee (max 10% = 1000 bps)
(define-public (set-protocol-fee-bps (new-fee-bps uint))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    (asserts! (<= new-fee-bps u1000) ERR-INVALID-PRICE) ;; Max 10%
    (var-set protocol-fee-bps new-fee-bps)
    (print { event: "protocol-fee-updated", new-fee-bps: new-fee-bps })
    (ok true)
  )
)

;; Set fee recipient
(define-public (set-fee-recipient (recipient principal))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    (var-set fee-recipient recipient)
    (print { event: "fee-recipient-updated", recipient: recipient })
    (ok true)
  )
)

;; Set escrow contract reference
(define-public (set-escrow-contract (contract principal))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    (var-set escrow-contract contract)
    (print { event: "escrow-contract-updated", contract: contract })
    (ok true)
  )
)

;; Set royalty contract reference
(define-public (set-royalty-contract (contract principal))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    (var-set royalty-contract contract)
    (print { event: "royalty-contract-updated", contract: contract })
    (ok true)
  )
)

;; Set auction contract reference
(define-public (set-auction-contract (contract principal))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    (var-set auction-contract contract)
    (print { event: "auction-contract-updated", contract: contract })
    (ok true)
  )
)

;; ============================================
;; EMERGENCY FUNCTIONS
;; ============================================

;; Force cancel listing (owner only, for emergencies)
(define-public (emergency-cancel-listing (listing-id uint))
  (let (
    (listing (unwrap! (map-get? listings listing-id) ERR-NOT-FOUND))
  )
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    
    ;; Deactivate listing
    (map-set listings listing-id (merge listing { is-active: false }))
    
    ;; Remove NFT mapping
    (map-delete nft-to-listing { 
      nft-contract: (get nft-contract listing), 
      token-id: (get token-id listing) 
    })
    
    (print {
      event: "emergency-listing-cancelled",
      listing-id: listing-id
    })
    
    (ok true)
  )
)

Functions (21)

FunctionAccessArgs
is-contract-ownerprivate
assert-not-pausedprivate
get-listingread-onlylisting-id: uint
get-listing-by-nftread-onlynft-contract: principal, token-id: uint
is-listedread-onlynft-contract: principal, token-id: uint
get-next-listing-idread-only
get-protocol-fee-bpsread-only
get-fee-recipientread-only
get-paused-statusread-only
get-statsread-only
get-seller-listing-countread-onlyseller: principal
get-purchase-breakdownread-onlylisting-id: uint
update-listing-pricepubliclisting-id: uint, new-price: uint
cancel-listingpubliclisting-id: uint
set-pausedpublicpaused: bool
set-protocol-fee-bpspublicnew-fee-bps: uint
set-fee-recipientpublicrecipient: principal
set-escrow-contractpubliccontract: principal
set-royalty-contractpubliccontract: principal
set-auction-contractpubliccontract: principal
emergency-cancel-listingpubliclisting-id: uint