Source Code

;; marketplace.clar - Clarity 4
;; Core marketplace for genetic data trading with expanded functionality

;; Import trait
(impl-trait .genetic-data-trait.genetic-data-trait)

;; Constants
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-INVALID-PRICE (err u101))
(define-constant ERR-LISTING-NOT-FOUND (err u102))
(define-constant ERR-INSUFFICIENT-BALANCE (err u103))
(define-constant ERR-INVALID-ACCESS-LEVEL (err u104))
(define-constant ERR-NO-VERIFIED-PROOFS (err u105))
(define-constant ERR-NO-VALID-CONSENT (err u106))
(define-constant ERR-PAYMENT-FAILED (err u107))
(define-constant ERR-ESCROW-EXISTS (err u108))
(define-constant ERR-ESCROW-NOT-FOUND (err u109))
(define-constant ERR-EXPIRED (err u110))
(define-constant ERR-NOT-FOUND (err u111))

;; Data maps
(define-map listings
    { listing-id: uint }
    {
        owner: principal,
        price: uint,
        data-contract: principal,
        data-id: uint,
        active: bool,
        access-level: uint,
        metadata-hash: (buff 32),
        requires-verification: bool,
        platform-fee-percent: uint,
        created-at: uint,                ;; Clarity 4: Unix timestamp
        updated-at: uint                 ;; Clarity 4: Unix timestamp
    }
)

(define-map user-purchases
    { user: principal, listing-id: uint }
    {
        purchase-time: uint,              ;; Clarity 4: Unix timestamp
        access-expiry: uint,              ;; Clarity 4: Unix timestamp
        access-level: uint,
        transaction-id: (buff 32),
        purchase-price: uint
    }
)

(define-map access-level-pricing
    { listing-id: uint, access-level: uint }
    {
        price: uint
    }
)

(define-map purchase-escrows
    { escrow-id: uint }
    {
        listing-id: uint,
        buyer: principal,
        amount: uint,
        created-at: uint,                 ;; Clarity 4: Unix timestamp
        expires-at: uint,                 ;; Clarity 4: Unix timestamp  
        released: bool,
        refunded: bool,
        access-level: uint
    }
)

(define-data-var next-escrow-id uint u1)
(define-data-var platform-fee-percent uint u250)
(define-data-var platform-address principal tx-sender)
(define-data-var marketplace-admin principal tx-sender)

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

(define-public (set-platform-fee (new-fee-percent uint))
    (begin
        (asserts! (is-eq tx-sender (var-get marketplace-admin)) ERR-NOT-AUTHORIZED)
        (asserts! (<= new-fee-percent u1000) ERR-INVALID-PRICE)
        (ok (var-set platform-fee-percent new-fee-percent))
    )
)

(define-public (set-platform-address (new-address principal))
    (begin
        (asserts! (is-eq tx-sender (var-get marketplace-admin)) ERR-NOT-AUTHORIZED)
        (ok (var-set platform-address new-address))
    )
)

(define-public (create-listing
    (listing-id uint)
    (price uint)
    (data-contract principal)
    (data-id uint)
    (access-level uint)
    (metadata-hash (buff 32))
    (requires-verification bool))

    (begin
        (asserts! (> price u0) ERR-INVALID-PRICE)
        (asserts! (> access-level u0) ERR-INVALID-ACCESS-LEVEL)
        (asserts! (<= access-level u3) ERR-INVALID-ACCESS-LEVEL)

        (map-set listings
            { listing-id: listing-id }
            {
                owner: tx-sender,
                price: price,
                data-contract: data-contract,
                data-id: data-id,
                active: true,
                access-level: access-level,
                metadata-hash: metadata-hash,
                requires-verification: requires-verification,
                platform-fee-percent: (var-get platform-fee-percent),
                created-at: stacks-block-time,    ;; Clarity 4: Unix timestamp
                updated-at: stacks-block-time     ;; Clarity 4: Unix timestamp
            }
        )

        (map-set access-level-pricing
            { listing-id: listing-id, access-level: access-level }
            { price: price }
        )

        (ok true)
    )
)

(define-public (set-access-level-price (listing-id uint) (access-level uint) (price uint))
    (let ((listing (unwrap! (map-get? listings { listing-id: listing-id }) ERR-LISTING-NOT-FOUND)))
        (asserts! (is-eq tx-sender (get owner listing)) ERR-NOT-AUTHORIZED)
        (asserts! (> price u0) ERR-INVALID-PRICE)
        (asserts! (> access-level u0) ERR-INVALID-ACCESS-LEVEL)
        (asserts! (<= access-level (get access-level listing)) ERR-INVALID-ACCESS-LEVEL)

        (map-set access-level-pricing
            { listing-id: listing-id, access-level: access-level }
            { price: price }
        )

        (ok true)
    )
)

(define-public (update-listing-status (listing-id uint) (active bool))
    (let ((listing (unwrap! (map-get? listings { listing-id: listing-id }) ERR-LISTING-NOT-FOUND)))
        (asserts! (is-eq tx-sender (get owner listing)) ERR-NOT-AUTHORIZED)

        (map-set listings
            { listing-id: listing-id }
            (merge listing {
                active: active,
                updated-at: stacks-block-time     ;; Clarity 4: Unix timestamp
            })
        )

        (ok true)
    )
)

(define-public (verify-purchase-eligibility (listing-id uint) (access-level uint))
    (let (
        (listing (unwrap! (map-get? listings { listing-id: listing-id }) ERR-LISTING-NOT-FOUND))
        (data-id (get data-id listing))
    )
        (asserts! (get active listing) ERR-LISTING-NOT-FOUND)
        (asserts! (> access-level u0) ERR-INVALID-ACCESS-LEVEL)
        (asserts! (<= access-level (get access-level listing)) ERR-INVALID-ACCESS-LEVEL)

        (if (get requires-verification listing)
            (begin
                (let ((verification-result (contract-call? .verification check-verified-proof data-id u1)))
                    (asserts! (is-ok verification-result) ERR-NO-VERIFIED-PROOFS)
                    (let ((proof-list (unwrap! verification-result ERR-NO-VERIFIED-PROOFS)))
                        (asserts! (> (len proof-list) u0) ERR-NO-VERIFIED-PROOFS)
                    )
                )
            )
            true
        )

        (let ((consent-result (contract-call? .compliance check-consent-validity data-id u1)))
            (asserts! (is-ok consent-result) ERR-NO-VALID-CONSENT)
            (let ((is-valid (unwrap! consent-result ERR-NO-VALID-CONSENT)))
                (asserts! is-valid ERR-NO-VALID-CONSENT)
            )
        )

        (ok true)
    )
)

(define-read-only (get-access-level-price (listing-id uint) (access-level uint))
    (match (map-get? access-level-pricing { listing-id: listing-id, access-level: access-level })
        price-info (ok (get price price-info))
        (match (map-get? listings { listing-id: listing-id })
            listing (ok (get price listing))
            (err ERR-LISTING-NOT-FOUND)
        )
    )
)

(define-public (create-purchase-escrow (listing-id uint) (access-level uint))
    (let (
        (listing (unwrap! (map-get? listings { listing-id: listing-id }) ERR-LISTING-NOT-FOUND))
        (escrow-id (var-get next-escrow-id))
    )
        (try! (verify-purchase-eligibility listing-id access-level))

        (let ((price (unwrap! (get-access-level-price listing-id access-level) ERR-INVALID-PRICE)))
            (asserts! (>= (stx-get-balance tx-sender) price) ERR-INSUFFICIENT-BALANCE)
            (var-set next-escrow-id (+ escrow-id u1))

            (map-set purchase-escrows
                { escrow-id: escrow-id }
                {
                    listing-id: listing-id,
                    buyer: tx-sender,
                    amount: price,
                    created-at: stacks-block-time,                  ;; Clarity 4: Unix timestamp
                    expires-at: (+ stacks-block-time u86400),       ;; Clarity 4: 24 hours in seconds
                    released: false,
                    refunded: false,
                    access-level: access-level
                }
            )

            (ok escrow-id)
        )
    )
)

(define-public (complete-purchase (escrow-id uint) (tx-id (buff 32)))
    (let (
        (escrow (unwrap! (map-get? purchase-escrows { escrow-id: escrow-id }) ERR-ESCROW-NOT-FOUND))
        (listing (unwrap! (map-get? listings { listing-id: (get listing-id escrow) }) ERR-LISTING-NOT-FOUND))
    )
        (asserts! (not (get released escrow)) ERR-NOT-AUTHORIZED)
        (asserts! (not (get refunded escrow)) ERR-NOT-AUTHORIZED)
        (asserts! (< stacks-block-time (get expires-at escrow)) ERR-EXPIRED)  ;; Clarity 4

        (map-set purchase-escrows
            { escrow-id: escrow-id }
            (merge escrow { released: true })
        )

        (let (
            (amount (get amount escrow))
            (fee-percent (get platform-fee-percent listing))
            (fee-amount (/ (* amount fee-percent) u10000))
            (seller-amount (- amount fee-amount))
        )
            (map-set user-purchases
                { user: (get buyer escrow), listing-id: (get listing-id escrow) }
                {
                    purchase-time: stacks-block-time,                ;; Clarity 4: Unix timestamp
                    access-expiry: (+ stacks-block-time u2592000),   ;; Clarity 4: ~30 days in seconds
                    access-level: (get access-level escrow),
                    transaction-id: tx-id,
                    purchase-price: amount
                }
            )

            (let ((log-result (contract-call? .compliance log-data-access
                (get data-id listing)
                u1
                tx-id
            )))
                (asserts! (is-ok log-result) ERR-PAYMENT-FAILED)
            )

            (let ((access-result (contract-call? .genetic-data grant-access
                (get data-id listing)
                (get buyer escrow)
                (get access-level escrow)
            )))
                (asserts! (is-ok access-result) ERR-PAYMENT-FAILED)
            )

            (ok true)
        )
    )
)

(define-public (refund-escrow (escrow-id uint))
    (let (
        (escrow (unwrap! (map-get? purchase-escrows { escrow-id: escrow-id }) ERR-ESCROW-NOT-FOUND))
    )
        (asserts! (not (get released escrow)) ERR-NOT-AUTHORIZED)
        (asserts! (not (get refunded escrow)) ERR-NOT-AUTHORIZED)

        (asserts! (or
            (>= stacks-block-time (get expires-at escrow))  ;; Clarity 4
            (is-eq tx-sender (get buyer escrow))
        ) ERR-NOT-AUTHORIZED)

        (map-set purchase-escrows
            { escrow-id: escrow-id }
            (merge escrow { refunded: true })
        )

        (ok true)
    )
)

(define-public (purchase-listing-direct (listing-id uint) (access-level uint) (tx-id (buff 32)))
    (let (
        (listing (unwrap! (map-get? listings { listing-id: listing-id }) ERR-LISTING-NOT-FOUND))
        (owner (get owner listing))
    )
        (let ((eligibility-result (verify-purchase-eligibility listing-id access-level)))
            (asserts! (is-ok eligibility-result) ERR-NO-VALID-CONSENT)
        )

        (let ((price (unwrap! (get-access-level-price listing-id access-level) ERR-INVALID-PRICE)))
            (asserts! (>= (stx-get-balance tx-sender) price) ERR-INSUFFICIENT-BALANCE)

            (let (
                (fee-percent (get platform-fee-percent listing))
                (fee-amount (/ (* price fee-percent) u10000))
                (seller-amount (- price fee-amount))
            )
                (map-set user-purchases
                    { user: tx-sender, listing-id: listing-id }
                    {
                        purchase-time: stacks-block-time,              ;; Clarity 4: Unix timestamp
                        access-expiry: (+ stacks-block-time u2592000), ;; Clarity 4: ~30 days in seconds
                        access-level: access-level,
                        transaction-id: tx-id,
                        purchase-price: price
                    }
                )

                (let ((log-result (contract-call? .compliance log-data-access
                    (get data-id listing)
                    u1
                    tx-id
                )))
                    (asserts! (is-ok log-result) ERR-PAYMENT-FAILED)
                )

                (let ((access-result (contract-call? .genetic-data grant-access
                    (get data-id listing)
                    tx-sender
                    access-level
                )))
                    (asserts! (is-ok access-result) ERR-PAYMENT-FAILED)
                )

                (ok true)
            )
        )
    )
)

;; Implement trait functions
(define-public (get-data-details (data-id uint))
    (match (map-get? listings { listing-id: data-id })
        listing (ok {
            owner: (get owner listing),
            price: (get price listing),
            access-level: (get access-level listing),
            metadata-hash: (get metadata-hash listing)
        })
        (err u404)
    )
)

(define-public (verify-access-rights (data-id uint) (user principal))
    (match (map-get? user-purchases { user: user, listing-id: data-id })
        purchase-data (ok (< stacks-block-time (get access-expiry purchase-data)))  ;; Clarity 4
        (err u404)
    )
)

(define-public (grant-access (data-id uint) (user principal) (access-level uint))
    (begin
        (asserts! (is-eq tx-sender (var-get marketplace-admin)) ERR-NOT-AUTHORIZED)
        (map-set user-purchases
            { user: user, listing-id: data-id }
            {
                purchase-time: stacks-block-time,              ;; Clarity 4: Unix timestamp
                access-expiry: (+ stacks-block-time u2592000), ;; Clarity 4: ~30 days in seconds
                access-level: access-level,
                transaction-id: 0x00,
                purchase-price: u0
            }
        )
        (ok true)
    )
)

;; Query functions
(define-read-only (get-listing (listing-id uint))
    (map-get? listings { listing-id: listing-id })
)

(define-read-only (get-user-purchase (user principal) (listing-id uint))
    (map-get? user-purchases { user: user, listing-id: listing-id })
)

(define-read-only (get-escrow (escrow-id uint))
    (map-get? purchase-escrows { escrow-id: escrow-id })
)

(define-public (extend-access (listing-id uint) (user principal) (duration uint))
    (let (
        (listing (unwrap! (map-get? listings { listing-id: listing-id }) ERR-LISTING-NOT-FOUND))
        (purchase (unwrap! (map-get? user-purchases { user: user, listing-id: listing-id }) ERR-NOT-FOUND))
    )
        (asserts! (or
            (is-eq tx-sender (get owner listing))
            (is-eq tx-sender (var-get marketplace-admin))
        ) ERR-NOT-AUTHORIZED)

        (map-set user-purchases
            { user: user, listing-id: listing-id }
            (merge purchase {
                access-expiry: (+ (get access-expiry purchase) duration)
            })
        )

        (ok true)
    )
)

Functions (19)

FunctionAccessArgs
set-adminpublicnew-admin: principal
set-platform-feepublicnew-fee-percent: uint
set-platform-addresspublicnew-address: principal
create-listingpubliclisting-id: uint, price: uint, data-contract: principal, data-id: uint, access-level: uint, metadata-hash: (buff 32
set-access-level-pricepubliclisting-id: uint, access-level: uint, price: uint
update-listing-statuspubliclisting-id: uint, active: bool
verify-purchase-eligibilitypubliclisting-id: uint, access-level: uint
get-access-level-priceread-onlylisting-id: uint, access-level: uint
create-purchase-escrowpubliclisting-id: uint, access-level: uint
complete-purchasepublicescrow-id: uint, tx-id: (buff 32
refund-escrowpublicescrow-id: uint
purchase-listing-directpubliclisting-id: uint, access-level: uint, tx-id: (buff 32
get-data-detailspublicdata-id: uint
verify-access-rightspublicdata-id: uint, user: principal
grant-accesspublicdata-id: uint, user: principal, access-level: uint
get-listingread-onlylisting-id: uint
get-user-purchaseread-onlyuser: principal, listing-id: uint
get-escrowread-onlyescrow-id: uint
extend-accesspubliclisting-id: uint, user: principal, duration: uint