Source Code

;; StackMart marketplace scaffold

(define-data-var next-id uint u1)
(define-data-var next-bundle-id uint u1)
(define-data-var next-pack-id uint u1)

(define-constant MAX_ROYALTY_BIPS u1000) ;; 10% in basis points
(define-constant BPS_DENOMINATOR u10000)
(define-constant ERR_BAD_ROYALTY (err u400))
(define-constant ERR_NOT_FOUND (err u404))
(define-constant ERR_NOT_OWNER (err u403))
(define-constant ERR_NFT_TRANSFER_FAILED (err u500))
(define-constant ERR_ESCROW_NOT_FOUND (err u404))
(define-constant ERR_INVALID_STATE (err u400))
(define-constant ERR_NOT_BUYER (err u403))
(define-constant ERR_NOT_SELLER (err u403))
(define-constant ERR_TIMEOUT_NOT_REACHED (err u400))
(define-constant ERR_ALREADY_ATTESTED (err u400))
(define-constant ERR_NOT_DELIVERED (err u400))
(define-constant ERR_DISPUTE_NOT_FOUND (err u404))
(define-constant ERR_DISPUTE_RESOLVED (err u400))
(define-constant ERR_INSUFFICIENT_STAKES (err u400))
(define-constant ERR_INVALID_SIDE (err u400))
(define-constant ERR_BUNDLE_NOT_FOUND (err u404))
(define-constant ERR_PACK_NOT_FOUND (err u404))
(define-constant ERR_INVALID_LISTING (err u400))
(define-constant ERR_BUNDLE_EMPTY (err u400))

;; Bundle and pack constants
(define-constant MAX_BUNDLE_SIZE u10)
(define-constant MAX_PACK_SIZE u20)
(define-constant MAX_DISCOUNT_BIPS u5000) ;; 50% max discount

;; Dispute resolution constants
(define-constant MIN_STAKE_AMOUNT u1000) ;; Minimum stake amount
(define-constant DISPUTE_RESOLUTION_THRESHOLD u5000) ;; Minimum total stakes to resolve

;; Escrow timeout: 144 blocks (approximately 1 day assuming 10 min blocks)
;; Note: Using burn-block-height for timeout calculation
(define-constant ESCROW_TIMEOUT_BLOCKS u144)

(define-map listings
  { id: uint }
  { seller: principal
  , price: uint
  , royalty-bips: uint
  , royalty-recipient: principal
  , nft-contract: (optional principal)
  , token-id: (optional uint)
  , license-terms: (optional (string-ascii 500))
  })

;; Escrow state: pending, delivered, confirmed, disputed, released, cancelled
(define-map escrows
  { listing-id: uint }
  { buyer: principal
  , amount: uint
  , created-at-block: uint
  , state: (string-ascii 20)
  , timeout-block: uint
  })

;; Reputation tracking for sellers and buyers
(define-map reputation
  { principal: principal }
  { successful-txs: uint
  , failed-txs: uint
  , rating-sum: uint
  , rating-count: uint
  })

;; Delivery attestations
(define-map delivery-attestations
  { listing-id: uint }
  { delivery-hash: (buff 32)
  , attested-at-block: uint
  , confirmed: bool
  , rejected: bool
  , rejection-reason: (optional (string-ascii 200))
  })

;; Transaction history tracking
(define-map transaction-history
  { principal: principal
  , tx-index: uint }
  { listing-id: uint
  , counterparty: principal
  , amount: uint
  , completed: bool
  , timestamp: uint
  })

(define-map tx-index-counter
  { principal: principal }
  uint)

;; Dispute resolution system
(define-data-var next-dispute-id uint u1)

(define-map disputes
  { id: uint }
  { escrow-id: uint
  , created-by: principal
  , reason: (string-ascii 500)
  , created-at-block: uint
  , resolved: bool
  , buyer-stakes: uint
  , seller-stakes: uint
  , resolution: (optional (string-ascii 20))
  })

(define-map dispute-stakes
  { dispute-id: uint
  , staker: principal }
  { amount: uint
  , side: bool
  })

(define-map dispute-votes
  { dispute-id: uint
  , voter: principal }
  { vote: bool
  , weight: uint
  })

;; Bundle and curated pack system
(define-map bundles
  { id: uint }
  { listing-ids: (list 10 uint)
  , discount-bips: uint
  , creator: principal
  , created-at-block: uint
  })

(define-map packs
  { id: uint }
  { listing-ids: (list 20 uint)
  , price: uint
  , curator: principal
  , created-at-block: uint
  })

(define-read-only (get-next-id)
  (ok (var-get next-id)))

(define-read-only (get-listing (id uint))
  (match (map-get? listings { id: id })
    listing (ok listing)
    ERR_NOT_FOUND))

;; get-listing-with-nft is an alias for get-listing (both return same data)
(define-read-only (get-listing-with-nft (id uint))
  (get-listing id))

(define-read-only (get-escrow-status (listing-id uint))
  (match (map-get? escrows { listing-id: listing-id })
    escrow (ok escrow)
    ERR_ESCROW_NOT_FOUND))

;; Shared default reputation structure
(define-constant DEFAULT_REPUTATION {
  successful-txs: u0
, failed-txs: u0
, rating-sum: u0
, rating-count: u0
})

(define-read-only (get-seller-reputation (seller principal))
  (ok (default-to DEFAULT_REPUTATION (map-get? reputation { principal: seller }))))

(define-read-only (get-buyer-reputation (buyer principal))
  (ok (default-to DEFAULT_REPUTATION (map-get? reputation { principal: buyer }))))

;; Verify NFT ownership using SIP-009 standard (get-owner function)
;; Note: NFT verification temporarily simplified - will be enhanced with proper trait support
(define-private (verify-nft-ownership (nft-contract principal) (token-id uint) (owner principal))
  ;; TODO: Implement proper NFT ownership verification with SIP-009 trait
  ;; For now, return true to allow listing creation (should be replaced with actual verification)
  true)

;; Legacy function - kept for backward compatibility (no NFT)
(define-public (create-listing (price uint) (royalty-bips uint) (royalty-recipient principal))
  (begin
    (asserts! (<= royalty-bips MAX_ROYALTY_BIPS) ERR_BAD_ROYALTY)
    (let ((id (var-get next-id)))
      (map-set listings
        { id: id }
        { seller: tx-sender
        , price: price
        , royalty-bips: royalty-bips
        , royalty-recipient: royalty-recipient
        , nft-contract: none
        , token-id: none
        , license-terms: none })
      (var-set next-id (+ id u1))
      (ok id))))

;; Create listing with NFT and license terms
(define-public (create-listing-with-nft
    (nft-contract principal)
    (token-id uint)
    (price uint)
    (royalty-bips uint)
    (royalty-recipient principal)
    (license-terms (string-ascii 500)))
  (begin
    (asserts! (<= royalty-bips MAX_ROYALTY_BIPS) ERR_BAD_ROYALTY)
    ;; Verify seller owns the NFT
    (asserts! (verify-nft-ownership nft-contract token-id tx-sender) ERR_NOT_OWNER)
    (let ((id (var-get next-id)))
      (map-set listings
        { id: id }
        { seller: tx-sender
        , price: price
        , royalty-bips: royalty-bips
        , royalty-recipient: royalty-recipient
        , nft-contract: (some nft-contract)
        , token-id: (some token-id)
        , license-terms: (some license-terms) })
      (var-set next-id (+ id u1))
      (ok id))))

;; Legacy immediate purchase (kept for backward compatibility)
(define-public (buy-listing (id uint))
  (match (map-get? listings { id: id })
    listing
      (let (
            (price (get price listing))
            (royalty-bips (get royalty-bips listing))
            (seller (get seller listing))
            (royalty-recipient (get royalty-recipient listing))
            (nft-contract-opt (get nft-contract listing))
            (token-id-opt (get token-id listing))
            (royalty (/ (* price royalty-bips) BPS_DENOMINATOR))
            (seller-share (- price royalty))
           )
        (begin
          ;; Transfer NFT if present (SIP-009 transfer function)
          ;; Note: Seller must authorize this contract to transfer on their behalf
          ;; TODO: Implement proper NFT transfer with SIP-009 trait support
          (match nft-contract-opt
            nft-contract-principal
              (match token-id-opt
                token-id-value
                  ;; NFT transfer temporarily disabled - requires trait implementation
                  true
                true)
            true)
          ;; Transfer payments
          (if (> royalty u0)
            (try! (stx-transfer? royalty tx-sender royalty-recipient))
            true)
          (try! (stx-transfer? seller-share tx-sender seller))
          (map-delete listings { id: id })
          (ok true)))
    ERR_NOT_FOUND))

;; Create escrow for listing purchase
;; Note: In Clarity, holding STX in contract requires the contract to receive funds first
;; For now, we track escrow state. Actual STX transfer happens on release.
(define-public (buy-listing-escrow (id uint))
  (match (map-get? listings { id: id })
    listing
      (begin
        ;; Check escrow doesn't already exist
        (asserts! (is-none (map-get? escrows { listing-id: id })) ERR_INVALID_STATE)
        (let (
              (price (get price listing))
             )
          (begin
            ;; Create escrow record
            ;; Note: STX should be transferred to contract address separately
            ;; Timeout mechanism can be added later with proper block height function
            (map-set escrows
              { listing-id: id }
              { buyer: tx-sender
              , amount: price
              , created-at-block: u0
              , state: "pending"
              , timeout-block: u0 })
            (ok true))))
    ERR_NOT_FOUND))

;; Seller attests delivery with delivery hash
(define-public (attest-delivery (listing-id uint) (delivery-hash (buff 32)))
  (match (map-get? escrows { listing-id: listing-id })
    escrow
      (match (map-get? listings { id: listing-id })
        listing
          (begin
            (asserts! (is-eq tx-sender (get seller listing)) ERR_NOT_SELLER)
            (asserts! (is-eq (get state escrow) "pending") ERR_INVALID_STATE)
            ;; Check attestation doesn't already exist
            (asserts! (is-none (map-get? delivery-attestations { listing-id: listing-id })) ERR_ALREADY_ATTESTED)
            ;; Transfer NFT if present
            (let ((nft-contract-opt (get nft-contract listing))
                  (token-id-opt (get token-id listing))
                  (buyer (get buyer escrow)))
              (begin
                ;; TODO: Implement proper NFT transfer with SIP-009 trait support
                (match nft-contract-opt
                  nft-contract-principal
                    (match token-id-opt
                      token-id-value
                        ;; NFT transfer temporarily disabled - requires trait implementation
                        true
                      true)
                  true)
                ;; Create delivery attestation
                (map-set delivery-attestations
                  { listing-id: listing-id }
                  { delivery-hash: delivery-hash
                  , attested-at-block: u0
                  , confirmed: false
                  , rejected: false
                  , rejection-reason: none })
                ;; Update escrow state to delivered
                (map-set escrows
                  { listing-id: listing-id }
                  { buyer: buyer
                  , amount: (get amount escrow)
                  , created-at-block: (get created-at-block escrow)
                  , state: "delivered"
                  , timeout-block: (get timeout-block escrow) })
                (ok true))))
        ERR_NOT_FOUND)
    ERR_ESCROW_NOT_FOUND))

;; Seller confirms delivery (legacy function - kept for backward compatibility)
;; Note: New code should use attest-delivery with actual delivery hash
(define-public (confirm-delivery (listing-id uint))
  ;; For legacy compatibility, use zero buffer (32 bytes)
  (let ((zero-hash 0x0000000000000000000000000000000000000000000000000000000000000000))
    (try! (attest-delivery listing-id zero-hash))
    (ok true)))

;; Buyer confirms receipt and releases escrow
(define-public (confirm-receipt (listing-id uint))
  (match (map-get? escrows { listing-id: listing-id })
    escrow
      (match (map-get? listings { id: listing-id })
        listing
          (begin
            (asserts! (is-eq tx-sender (get buyer escrow)) ERR_NOT_BUYER)
            (asserts! (is-eq (get state escrow) "delivered") ERR_INVALID_STATE)
            ;; Release escrow payments
            (let (
                  (price (get amount escrow))
                  (royalty-bips (get royalty-bips listing))
                  (seller (get seller listing))
                  (royalty-recipient (get royalty-recipient listing))
                  (royalty (/ (* price royalty-bips) BPS_DENOMINATOR))
                  (seller-share (- price royalty))
                 )
              (begin
                ;; Transfer payments from escrow
                ;; Note: In a full implementation, STX would be transferred from contract-held escrow
                ;; For now, this is a placeholder - actual transfer requires contract to hold funds
                (if (> royalty u0)
                  (try! (stx-transfer? royalty tx-sender royalty-recipient))
                  true)
                (try! (stx-transfer? seller-share tx-sender seller))
                ;; Update delivery attestation if exists
                (match (map-get? delivery-attestations { listing-id: listing-id })
                  attestation
                    (map-set delivery-attestations
                      { listing-id: listing-id }
                      { delivery-hash: (get delivery-hash attestation)
                      , attested-at-block: (get attested-at-block attestation)
                      , confirmed: true
                      , rejected: false
                      , rejection-reason: none })
                  true)
                ;; Update reputation - successful transaction
                (update-reputation seller true)
                (update-reputation tx-sender true)
                ;; Update escrow state
                (map-set escrows
                  { listing-id: listing-id }
                  { buyer: (get buyer escrow)
                  , amount: price
                  , created-at-block: (get created-at-block escrow)
                  , state: "confirmed"
                  , timeout-block: (get timeout-block escrow) })
                ;; Record transaction history
                (record-transaction seller listing-id tx-sender price true)
                (record-transaction tx-sender listing-id seller price true)
                ;; Remove listing
                (map-delete listings { id: listing-id })
                (ok true))))
        ERR_NOT_FOUND)
    ERR_ESCROW_NOT_FOUND))

;; Buyer confirms delivery received (alias for confirm-receipt)
(define-public (confirm-delivery-received (listing-id uint))
  (confirm-receipt listing-id))

;; Buyer rejects delivery
(define-public (reject-delivery (listing-id uint) (reason (string-ascii 200)))
  (match (map-get? escrows { listing-id: listing-id })
    escrow
      (match (map-get? listings { id: listing-id })
        listing
          (begin
            (asserts! (is-eq tx-sender (get buyer escrow)) ERR_NOT_BUYER)
            (asserts! (is-eq (get state escrow) "delivered") ERR_INVALID_STATE)
            ;; Update delivery attestation
            (match (map-get? delivery-attestations { listing-id: listing-id })
              attestation
                (map-set delivery-attestations
                  { listing-id: listing-id }
                  { delivery-hash: (get delivery-hash attestation)
                  , attested-at-block: (get attested-at-block attestation)
                  , confirmed: false
                  , rejected: true
                  , rejection-reason: (some reason) })
              true)
            ;; Update reputation - failed transaction
            (update-reputation (get seller listing) false)
            (update-reputation tx-sender false)
            ;; Record transaction history
            (let ((price (get amount escrow)))
              (begin
                (record-transaction (get seller listing) listing-id tx-sender price false)
                (record-transaction tx-sender listing-id (get seller listing) price false)
                (ok true))))
        ERR_NOT_FOUND)
    ERR_ESCROW_NOT_FOUND))

;; Release escrow after timeout or manual release
(define-public (release-escrow (listing-id uint))
  (match (map-get? escrows { listing-id: listing-id })
    escrow
      (match (map-get? listings { id: listing-id })
        listing
          (let (
                (state (get state escrow))
               )
            (begin
              ;; Can release if: state is "delivered" (buyer can release after delivery)
              ;; Timeout check can be added later with proper block height function
              (asserts! (is-eq state "delivered") ERR_TIMEOUT_NOT_REACHED)
              ;; Only buyer or seller can release after timeout
              (asserts! (or (is-eq tx-sender (get buyer escrow)) (is-eq tx-sender (get seller listing))) ERR_NOT_OWNER)
              ;; If delivered and timeout, release to seller (seller fulfilled, buyer didn't confirm)
              ;; If pending and timeout, refund to buyer
              (let (
                    (price (get amount escrow))
                    (seller (get seller listing))
                    (buyer-addr (get buyer escrow))
                    (timeout-block (get timeout-block escrow))
                   )
                (begin
                  (if (is-eq state "delivered")
                    ;; Seller delivered, buyer didn't confirm - release to seller
                    (let (
                          (royalty-bips (get royalty-bips listing))
                          (royalty-recipient (get royalty-recipient listing))
                          (royalty (/ (* price royalty-bips) BPS_DENOMINATOR))
                          (seller-share (- price royalty))
                         )
                      (begin
                        ;; Note: In full implementation, transfer from contract-held escrow
                        (if (> royalty u0)
                          (try! (stx-transfer? royalty tx-sender royalty-recipient))
                          true)
                        (try! (stx-transfer? seller-share tx-sender seller))))
                    ;; Pending and timeout - refund to buyer
                    (try! (stx-transfer? price tx-sender buyer-addr)))
                  ;; Update escrow state
                  (map-set escrows
                    { listing-id: listing-id }
                    { buyer: buyer-addr
                    , amount: price
                    , created-at-block: (get created-at-block escrow)
                    , state: "released"
                    , timeout-block: timeout-block })
                  ;; Remove listing if released
                  (if (is-eq state "delivered")
                    (map-delete listings { id: listing-id })
                    true)
                  (ok true)))))
        ERR_NOT_FOUND)
    ERR_ESCROW_NOT_FOUND))

;; Cancel escrow (only if pending and by buyer or seller)
(define-public (cancel-escrow (listing-id uint))
  (match (map-get? escrows { listing-id: listing-id })
    escrow
      (match (map-get? listings { id: listing-id })
        listing
          (begin
            (asserts! (is-eq (get state escrow) "pending") ERR_INVALID_STATE)
            (asserts! (or (is-eq tx-sender (get buyer escrow)) (is-eq tx-sender (get seller listing))) ERR_NOT_OWNER)
            ;; Refund to buyer
            (let ((price (get amount escrow))
                  (buyer-addr (get buyer escrow)))
              (begin
                ;; Note: In full implementation, transfer from contract-held escrow
                (try! (stx-transfer? price tx-sender buyer-addr))
                ;; Update escrow state
                (map-set escrows
                  { listing-id: listing-id }
                  { buyer: buyer-addr
                  , amount: price
                  , created-at-block: (get created-at-block escrow)
                  , state: "cancelled"
                  , timeout-block: (get timeout-block escrow) })
                (ok true))))
        ERR_NOT_FOUND)
    ERR_ESCROW_NOT_FOUND))





;; Helper function to update reputation (optimized)
(define-private (update-reputation (principal principal) (success bool))
  (let ((current-rep (default-to DEFAULT_REPUTATION (map-get? reputation { principal: principal }))))
    (if success
      (map-set reputation
        { principal: principal }
        { successful-txs: (+ (get successful-txs current-rep) u1)
        , failed-txs: (get failed-txs current-rep)
        , rating-sum: (get rating-sum current-rep)
        , rating-count: (get rating-count current-rep) })
      (map-set reputation
        { principal: principal }
        { successful-txs: (get successful-txs current-rep)
        , failed-txs: (+ (get failed-txs current-rep) u1)
        , rating-sum: (get rating-sum current-rep)
        , rating-count: (get rating-count current-rep) }))))

;; Helper function to record transaction history
(define-private (record-transaction (principal principal) (listing-id uint) (counterparty principal) (amount uint) (completed bool))
  (let ((current-index (default-to u0 (map-get? tx-index-counter { principal: principal }))))
    (begin
      (map-set transaction-history
        { principal: principal
        , tx-index: current-index }
        { listing-id: listing-id
        , counterparty: counterparty
        , amount: amount
        , completed: completed
        , timestamp: u0 })
      (map-set tx-index-counter
        { principal: principal }
        (+ current-index u1)))))

;; Get transaction history for a principal (returns transaction by index)
(define-read-only (get-transaction-history (principal principal) (index uint))
  (match (map-get? transaction-history { principal: principal, tx-index: index })
    tx (ok tx)
    ERR_NOT_FOUND))

(define-read-only (get-dispute (dispute-id uint))
  (match (map-get? disputes { id: dispute-id })
    dispute (ok dispute)
    ERR_DISPUTE_NOT_FOUND))

(define-read-only (get-dispute-stakes (dispute-id uint) (staker principal))
  (match (map-get? dispute-stakes { dispute-id: dispute-id, staker: staker })
    stake (ok stake)
    ERR_NOT_FOUND))

;; Create a dispute for an escrow
(define-public (create-dispute (escrow-id uint) (reason (string-ascii 500)))
  (match (map-get? escrows { listing-id: escrow-id })
    escrow
      (match (map-get? listings { id: escrow-id })
        listing
          (begin
            ;; Only buyer or seller can create dispute
            (asserts! (or (is-eq tx-sender (get buyer escrow)) (is-eq tx-sender (get seller listing))) ERR_NOT_OWNER)
            ;; Escrow must be in delivered state
            (asserts! (is-eq (get state escrow) "delivered") ERR_INVALID_STATE)
            ;; Check dispute doesn't already exist
            (let ((dispute-id (var-get next-dispute-id)))
              (begin
                (asserts! (is-none (map-get? disputes { id: dispute-id })) ERR_INVALID_STATE)
                ;; Create dispute
                (map-set disputes
                  { id: dispute-id }
                  { escrow-id: escrow-id
                  , created-by: tx-sender
                  , reason: reason
                  , created-at-block: u0
                  , resolved: false
                  , buyer-stakes: u0
                  , seller-stakes: u0
                  , resolution: none })
                ;; Update escrow state to disputed
                (map-set escrows
                  { listing-id: escrow-id }
                  { buyer: (get buyer escrow)
                  , amount: (get amount escrow)
                  , created-at-block: (get created-at-block escrow)
                  , state: "disputed"
                  , timeout-block: (get timeout-block escrow) })
                (var-set next-dispute-id (+ dispute-id u1))
                (ok dispute-id))))
        ERR_NOT_FOUND)
    ERR_ESCROW_NOT_FOUND))

;; Stake on a dispute (side: true = buyer, false = seller)
(define-public (stake-on-dispute (dispute-id uint) (amount uint) (side bool))
  (match (map-get? disputes { id: dispute-id })
    dispute
      (begin
        ;; Dispute must not be resolved
        (asserts! (not (get resolved dispute)) ERR_DISPUTE_RESOLVED)
        ;; Minimum stake amount
        (asserts! (>= amount MIN_STAKE_AMOUNT) ERR_INSUFFICIENT_STAKES)
        ;; Transfer stake amount (placeholder - in full implementation, contract would hold stakes)
        ;; For now, we track the stake amount
        (let ((current-stake (default-to { amount: u0, side: false } (map-get? dispute-stakes { dispute-id: dispute-id, staker: tx-sender }))))
          (begin
            ;; Update or create stake
            (map-set dispute-stakes
              { dispute-id: dispute-id
              , staker: tx-sender }
              { amount: (+ (get amount current-stake) amount)
              , side: side })
            ;; Update dispute stakes totals (optimized)
            (let ((buyer-stakes-new (if side (+ (get buyer-stakes dispute) amount) (get buyer-stakes dispute)))
                  (seller-stakes-new (if side (get seller-stakes dispute) (+ (get seller-stakes dispute) amount))))
              (map-set disputes
                { id: dispute-id }
                { escrow-id: (get escrow-id dispute)
                , created-by: (get created-by dispute)
                , reason: (get reason dispute)
                , created-at-block: (get created-at-block dispute)
                , resolved: (get resolved dispute)
                , buyer-stakes: buyer-stakes-new
                , seller-stakes: seller-stakes-new
                , resolution: (get resolution dispute) }))
            (ok true))))
    ERR_DISPUTE_NOT_FOUND))

;; Vote on a dispute (weighted by stake amount)
(define-public (vote-on-dispute (dispute-id uint) (vote bool))
  (match (map-get? disputes { id: dispute-id })
    dispute
      (match (map-get? dispute-stakes { dispute-id: dispute-id, staker: tx-sender })
        stake
          (begin
            ;; Dispute must not be resolved
            (asserts! (not (get resolved dispute)) ERR_DISPUTE_RESOLVED)
            ;; Must have staked to vote
            (asserts! (> (get amount stake) u0) ERR_INSUFFICIENT_STAKES)
            ;; Vote must match stake side
            (asserts! (is-eq vote (get side stake)) ERR_INVALID_SIDE)
            ;; Record vote with weight = stake amount
            (map-set dispute-votes
              { dispute-id: dispute-id
              , voter: tx-sender }
              { vote: vote
              , weight: (get amount stake) })
            (ok true))
        ERR_NOT_FOUND)
    ERR_DISPUTE_NOT_FOUND))

;; Resolve dispute based on weighted votes
(define-public (resolve-dispute (dispute-id uint))
  (match (map-get? disputes { id: dispute-id })
    dispute
      (begin
        ;; Dispute must not be resolved
        (asserts! (not (get resolved dispute)) ERR_DISPUTE_RESOLVED)
        ;; Must have minimum stakes to resolve
        (let ((total-stakes (+ (get buyer-stakes dispute) (get seller-stakes dispute))))
          (begin
            (asserts! (>= total-stakes DISPUTE_RESOLUTION_THRESHOLD) ERR_INSUFFICIENT_STAKES)
            ;; Calculate weighted votes (simplified - in full implementation would iterate all votes)
            ;; For now, use stake amounts as proxy for votes
            (let ((buyer-stakes (get buyer-stakes dispute))
                  (seller-stakes (get seller-stakes dispute))
                  (escrow-id (get escrow-id dispute)))
              (begin
                ;; Determine winner based on stake amounts
                (if (> buyer-stakes seller-stakes)
                  ;; Buyer wins - release to buyer
                  (begin
                    ;; Mark dispute as resolved
                    (map-set disputes
                      { id: dispute-id }
                      { escrow-id: escrow-id
                      , created-by: (get created-by dispute)
                      , reason: (get reason dispute)
                      , created-at-block: (get created-at-block dispute)
                      , resolved: true
                      , buyer-stakes: buyer-stakes
                      , seller-stakes: seller-stakes
                      , resolution: (some "buyer") })
                    ;; Refund buyer
                    (try! (match (map-get? escrows { listing-id: escrow-id })
                      escrow
                        (let ((price (get amount escrow))
                              (buyer-addr (get buyer escrow)))
                          (begin
                            ;; Note: In full implementation, transfer from contract-held escrow
                            (try! (stx-transfer? price tx-sender buyer-addr))
                            (map-set escrows
                              { listing-id: escrow-id }
                              { buyer: buyer-addr
                              , amount: price
                              , created-at-block: (get created-at-block escrow)
                              , state: "released"
                              , timeout-block: (get timeout-block escrow) })
                            (map-delete listings { id: escrow-id })
                            (ok true)))
                      ERR_ESCROW_NOT_FOUND))
                    true)
                  ;; Seller wins - release to seller
                  (begin
                    ;; Mark dispute as resolved
                    (map-set disputes
                      { id: dispute-id }
                      { escrow-id: escrow-id
                      , created-by: (get created-by dispute)
                      , reason: (get reason dispute)
                      , created-at-block: (get created-at-block dispute)
                      , resolved: true
                      , buyer-stakes: buyer-stakes
                      , seller-stakes: seller-stakes
                      , resolution: (some "seller") })
                    ;; Release to seller
                    (try! (match (map-get? escrows { listing-id: escrow-id })
                      escrow
                        (match (map-get? listings { id: escrow-id })
                          listing
                            (let ((price (get amount escrow))
                                  (seller (get seller listing))
                                  (royalty-bips (get royalty-bips listing))
                                  (royalty-recipient (get royalty-recipient listing))
                                  (royalty (/ (* price royalty-bips) BPS_DENOMINATOR))
                                  (seller-share (- price royalty)))
                              (begin
                                ;; Note: In full implementation, transfer from contract-held escrow
                                (if (> royalty u0)
                                  (try! (stx-transfer? royalty tx-sender royalty-recipient))
                                  true)
                                (try! (stx-transfer? seller-share tx-sender seller))
                                (map-set escrows
                                  { listing-id: escrow-id }
                                  { buyer: (get buyer escrow)
                                  , amount: price
                                  , created-at-block: (get created-at-block escrow)
                                  , state: "released"
                                  , timeout-block: (get timeout-block escrow) })
                                (map-delete listings { id: escrow-id })
                                (ok true)))
                          ERR_NOT_FOUND)
                      ERR_ESCROW_NOT_FOUND))
                    true)))
                (ok true)))))
    ERR_DISPUTE_NOT_FOUND))

(define-read-only (get-bundle (bundle-id uint))
  (match (map-get? bundles { id: bundle-id })
    bundle (ok bundle)
    ERR_BUNDLE_NOT_FOUND))

(define-read-only (get-pack (pack-id uint))
  (match (map-get? packs { id: pack-id })
    pack (ok pack)
    ERR_PACK_NOT_FOUND))

;; Create a bundle of listings with discount
(define-public (create-bundle (listing-ids (list 10 uint)) (discount-bips uint))
  (begin
    ;; Validate bundle not empty
    (asserts! (> (len listing-ids) u0) ERR_BUNDLE_EMPTY)
    ;; Validate discount within limits
    (asserts! (<= discount-bips MAX_DISCOUNT_BIPS) ERR_BAD_ROYALTY)
    ;; Validate all listings exist and belong to creator
    ;; Note: In full implementation, would validate each listing
    (let ((bundle-id (var-get next-bundle-id)))
      (begin
        (map-set bundles
          { id: bundle-id }
          { listing-ids: listing-ids
          , discount-bips: discount-bips
          , creator: tx-sender
          , created-at-block: u0 })
        (var-set next-bundle-id (+ bundle-id u1))
        (ok bundle-id)))))

;; Buy a bundle (purchases all listings in bundle with discount)
(define-public (buy-bundle (bundle-id uint))
  (match (map-get? bundles { id: bundle-id })
    bundle
      (let ((listing-ids (get listing-ids bundle))
            (discount-bips (get discount-bips bundle)))
        (begin
          ;; Calculate total price with discount
          (let ((total-price (calculate-bundle-price listing-ids discount-bips)))
            (begin
              ;; Transfer payment
              ;; Note: In full implementation, would handle each listing purchase
              ;; For now, simplified - actual payment would be calculated from individual listings
              true
              ;; Process each listing purchase
              (process-bundle-purchases listing-ids tx-sender)
              ;; Delete bundle after purchase
              (map-delete bundles { id: bundle-id })
              (ok true)))))
    ERR_BUNDLE_NOT_FOUND))

;; Helper function to calculate bundle price
;; Note: Simplified calculation - in full implementation would iterate through all listings
(define-private (calculate-bundle-price (listing-ids (list 10 uint)) (discount-bips uint))
  ;; For now, return a placeholder price
  ;; In full implementation, would sum all listing prices and apply discount
  u0)

;; Helper function to process bundle purchases
(define-private (process-bundle-purchases (listing-ids (list 10 uint)) (buyer principal))
  ;; Note: Simplified - in full implementation would process each listing
  true)

;; Create a curated pack
(define-public (create-curated-pack (listing-ids (list 20 uint)) (pack-price uint) (curator principal))
  (begin
    ;; Validate pack not empty
    (asserts! (> (len listing-ids) u0) ERR_BUNDLE_EMPTY)
    ;; Validate curator is tx-sender
    (asserts! (is-eq tx-sender curator) ERR_NOT_OWNER)
    ;; Validate all listings exist
    ;; Note: In full implementation, would validate each listing
    (let ((pack-id (var-get next-pack-id)))
      (begin
        (map-set packs
          { id: pack-id }
          { listing-ids: listing-ids
          , price: pack-price
          , curator: curator
          , created-at-block: u0 })
        (var-set next-pack-id (+ pack-id u1))
        (ok pack-id)))))

;; Buy a curated pack
(define-public (buy-curated-pack (pack-id uint))
  (match (map-get? packs { id: pack-id })
    pack
      (let ((listing-ids (get listing-ids pack))
            (pack-price (get price pack))
            (curator (get curator pack)))
        (begin
          ;; Transfer payment to curator (simplified - in full would split)
          (try! (stx-transfer? pack-price tx-sender curator))
          ;; Process each listing purchase
          (process-pack-purchases listing-ids tx-sender)
          ;; Delete pack after purchase
          (map-delete packs { id: pack-id })
          (ok true)))
    ERR_PACK_NOT_FOUND))

;; Helper function to process pack purchases
(define-private (process-pack-purchases (listing-ids (list 20 uint)) (buyer principal))
  ;; Note: Simplified - in full implementation would process each listing
  true)

Functions (36)

FunctionAccessArgs
get-next-idread-only
get-listingread-onlyid: uint
get-listing-with-nftread-onlyid: uint
get-escrow-statusread-onlylisting-id: uint
get-seller-reputationread-onlyseller: principal
get-buyer-reputationread-onlybuyer: principal
verify-nft-ownershipprivatenft-contract: principal, token-id: uint, owner: principal
create-listingpublicprice: uint, royalty-bips: uint, royalty-recipient: principal
create-listing-with-nftpublicnft-contract: principal, token-id: uint, price: uint, royalty-bips: uint, royalty-recipient: principal, license-terms: (string-ascii 500
buy-listingpublicid: uint
buy-listing-escrowpublicid: uint
attest-deliverypubliclisting-id: uint, delivery-hash: (buff 32
confirm-deliverypubliclisting-id: uint
confirm-receiptpubliclisting-id: uint
confirm-delivery-receivedpubliclisting-id: uint
reject-deliverypubliclisting-id: uint, reason: (string-ascii 200
release-escrowpubliclisting-id: uint
cancel-escrowpubliclisting-id: uint
update-reputationprivateprincipal: principal, success: bool
record-transactionprivateprincipal: principal, listing-id: uint, counterparty: principal, amount: uint, completed: bool
get-transaction-historyread-onlyprincipal: principal, index: uint
get-disputeread-onlydispute-id: uint
get-dispute-stakesread-onlydispute-id: uint, staker: principal
create-disputepublicescrow-id: uint, reason: (string-ascii 500
stake-on-disputepublicdispute-id: uint, amount: uint, side: bool
vote-on-disputepublicdispute-id: uint, vote: bool
resolve-disputepublicdispute-id: uint
get-bundleread-onlybundle-id: uint
get-packread-onlypack-id: uint
create-bundlepubliclisting-ids: (list 10 uint
buy-bundlepublicbundle-id: uint
calculate-bundle-priceprivatelisting-ids: (list 10 uint
process-bundle-purchasesprivatelisting-ids: (list 10 uint
create-curated-packpubliclisting-ids: (list 20 uint
buy-curated-packpublicpack-id: uint
process-pack-purchasesprivatelisting-ids: (list 20 uint