Source Code

;; StackStream Micropayment Gateway Contract
;; Facilitates instant pay-per-view content access with minimal friction

;; Constants
(define-constant contract-owner tx-sender)
(define-constant err-owner-only (err u300))
(define-constant err-not-found (err u301))
(define-constant err-already-exists (err u302))
(define-constant err-insufficient-funds (err u303))
(define-constant err-unauthorized (err u304))
(define-constant err-invalid-input (err u305))
(define-constant err-expired (err u306))
(define-constant err-already-accessed (err u307))

;; Fee constants
(define-constant transaction-fee-percent u500) ;; 5% = 500/10000
(define-constant min-transaction-fee u50000) ;; 0.05 STX minimum
(define-constant batch-discount-percent u1000) ;; 10% = 1000/10000
(define-constant batch-threshold u10) ;; 10+ items for discount

;; Price range constants (in microSTX)
(define-constant min-content-price u100000) ;; 0.1 STX
(define-constant max-content-price u10000000) ;; 10 STX

;; Access token validity (in blocks, ~24 hours)
(define-constant access-token-validity u144)

;; Data Variables
(define-data-var total-transactions uint u0)
(define-data-var total-volume uint u0)
(define-data-var platform-treasury principal contract-owner)
(define-data-var next-access-token-id uint u0)

;; Data Maps

;; Content access tokens (time-limited)
(define-map access-tokens
    uint ;; token-id
    {
        purchaser: principal,
        creator: principal,
        content-id: uint,
        purchase-block: uint,
        expiry-block: uint,
        amount-paid: uint,
        is-active: bool,
        access-key: (buff 32)
    }
)

;; User access token tracking
(define-map user-access-tokens
    {purchaser: principal, creator: principal, content-id: uint}
    uint ;; token-id
)

;; Transaction history
(define-map transactions
    uint ;; transaction-id
    {
        purchaser: principal,
        creator: principal,
        content-ids: (list 10 uint),
        total-amount: uint,
        platform-fee: uint,
        creator-amount: uint,
        transaction-block: uint,
        is-batch: bool
    }
)

;; Creator payment stats
(define-map creator-payment-stats
    principal
    {
        total-transactions: uint,
        total-revenue: uint,
        total-content-sold: uint
    }
)

;; User purchase history count
(define-map user-transaction-count principal uint)

;; Content bundles
(define-map content-bundles
    {creator: principal, bundle-id: uint}
    {
        content-ids: (list 10 uint),
        bundle-price: uint,
        discount-percent: uint,
        is-active: bool,
        created-at: uint
    }
)

(define-map creator-bundle-count principal uint)

;; Gift tracking
(define-map gift-access
    {gifter: principal, recipient: principal, gift-id: uint}
    {
        creator: principal,
        content-id: uint,
        amount: uint,
        gifted-at: uint,
        is-claimed: bool
    }
)

(define-map user-gift-count principal uint)

;; Private Functions

(define-private (calculate-fee (amount uint))
    (let ((calculated-fee (/ (* amount transaction-fee-percent) u10000)))
        (if (< calculated-fee min-transaction-fee)
            min-transaction-fee
            calculated-fee
        )
    )
)

(define-private (calculate-creator-amount (amount uint) (fee uint))
    (- amount fee)
)

(define-private (apply-batch-discount (total-amount uint) (item-count uint))
    (if (>= item-count batch-threshold)
        (let ((discount (/ (* total-amount batch-discount-percent) u10000)))
            (- total-amount discount)
        )
        total-amount
    )
)

(define-private (generate-access-key (purchaser principal) (content-id uint) (block uint))
    ;; Simple hash generation using available data
    (sha256 (concat 
        (concat (unwrap-panic (to-consensus-buff? purchaser)) 
                (unwrap-panic (to-consensus-buff? content-id)))
        (unwrap-panic (to-consensus-buff? block))
    ))
)

(define-private (update-creator-payment-stats (creator principal) (amount uint) (content-count uint))
    (let (
        (stats (default-to 
            {total-transactions: u0, total-revenue: u0, total-content-sold: u0}
            (map-get? creator-payment-stats creator)
        ))
    )
        (map-set creator-payment-stats creator {
            total-transactions: (+ (get total-transactions stats) u1),
            total-revenue: (+ (get total-revenue stats) amount),
            total-content-sold: (+ (get total-content-sold stats) content-count)
        })
        true
    )
)

(define-private (process-payment (amount uint) (creator principal))
    (let (
        (fee (calculate-fee amount))
        (creator-amount (calculate-creator-amount amount fee))
    )
        ;; Transfer to creator
        (try! (stx-transfer? creator-amount tx-sender creator))
        
        ;; Transfer fee to platform
        (try! (stx-transfer? fee tx-sender (var-get platform-treasury)))
        
        (ok {fee: fee, creator-amount: creator-amount})
    )
)

;; Public Functions

;; Purchase single content piece
(define-public (purchase-content (creator principal) (content-id uint) (price uint))
    (let (
        (purchaser tx-sender)
        (access-key {purchaser: purchaser, creator: creator, content-id: content-id})
    )
        ;; Validate inputs
        (asserts! (and (>= price min-content-price) (<= price max-content-price)) err-invalid-input)
        (asserts! (is-none (map-get? user-access-tokens access-key)) err-already-accessed)
        
        ;; Process payment
        (let ((payment-result (try! (process-payment price creator))))
            
            ;; Generate access token
            (let (
                (token-id (var-get next-access-token-id))
                (access-key-hash (generate-access-key purchaser content-id stacks-block-height))
            )
                (map-set access-tokens token-id {
                    purchaser: purchaser,
                    creator: creator,
                    content-id: content-id,
                    purchase-block: stacks-block-height,
                    expiry-block: (+ stacks-block-height access-token-validity),
                    amount-paid: price,
                    is-active: true,
                    access-key: access-key-hash
                })
                
                ;; Map user to token
                (map-set user-access-tokens access-key token-id)
                
                ;; Increment token ID
                (var-set next-access-token-id (+ token-id u1))
                
                ;; Record transaction
                (let ((tx-count (var-get total-transactions)))
                    (map-set transactions tx-count {
                        purchaser: purchaser,
                        creator: creator,
                        content-ids: (list content-id),
                        total-amount: price,
                        platform-fee: (get fee payment-result),
                        creator-amount: (get creator-amount payment-result),
                        transaction-block: stacks-block-height,
                        is-batch: false
                    })
                    (var-set total-transactions (+ tx-count u1))
                )
                
                ;; Update user transaction count
                (map-set user-transaction-count purchaser 
                    (+ (default-to u0 (map-get? user-transaction-count purchaser)) u1))
                
                ;; Update stats
                (update-creator-payment-stats creator (get creator-amount payment-result) u1)
                (var-set total-volume (+ (var-get total-volume) price))
                
                (ok token-id)
            )
        )
    )
)

;; Purchase content bundle with batch discount
(define-public (purchase-batch (creator principal) (content-ids (list 10 uint)) (total-price uint))
    (let (
        (purchaser tx-sender)
        (item-count (len content-ids))
        (discounted-price (apply-batch-discount total-price item-count))
    )
        ;; Validate inputs
        (asserts! (> item-count u0) err-invalid-input)
        (asserts! (and (>= discounted-price min-content-price) 
                      (<= discounted-price (* max-content-price item-count))) 
                  err-invalid-input)
        
        ;; Process payment with discount
        (let ((payment-result (try! (process-payment discounted-price creator))))
            
            ;; Record transaction
            (let ((tx-count (var-get total-transactions)))
                (map-set transactions tx-count {
                    purchaser: purchaser,
                    creator: creator,
                    content-ids: content-ids,
                    total-amount: discounted-price,
                    platform-fee: (get fee payment-result),
                    creator-amount: (get creator-amount payment-result),
                    transaction-block: stacks-block-height,
                    is-batch: true
                })
                (var-set total-transactions (+ tx-count u1))
            )
            
            ;; Update stats
            (update-creator-payment-stats creator (get creator-amount payment-result) item-count)
            (var-set total-volume (+ (var-get total-volume) discounted-price))
            
            ;; Update user transaction count
            (map-set user-transaction-count purchaser 
                (+ (default-to u0 (map-get? user-transaction-count purchaser)) u1))
            
            (ok true)
        )
    )
)

;; Create content bundle
(define-public (create-bundle (content-ids (list 10 uint)) 
                              (bundle-price uint) 
                              (discount-percent uint))
    (let (
        (creator tx-sender)
        (bundle-count (default-to u0 (map-get? creator-bundle-count creator)))
        (bundle-id (+ bundle-count u1))
    )
        ;; Validate inputs
        (asserts! (> (len content-ids) u1) err-invalid-input)
        (asserts! (> bundle-price u0) err-invalid-input)
        (asserts! (<= discount-percent u5000) err-invalid-input) ;; Max 50% discount
        
        ;; Create bundle
        (map-set content-bundles {creator: creator, bundle-id: bundle-id} {
            content-ids: content-ids,
            bundle-price: bundle-price,
            discount-percent: discount-percent,
            is-active: true,
            created-at: stacks-block-height
        })
        
        ;; Update bundle count
        (map-set creator-bundle-count creator bundle-id)
        
        (ok bundle-id)
    )
)

;; Purchase bundle by ID
(define-public (purchase-bundle (creator principal) (bundle-id uint))
    (let (
        (purchaser tx-sender)
        (bundle-key {creator: creator, bundle-id: bundle-id})
        (bundle-data (unwrap! (map-get? content-bundles bundle-key) err-not-found))
    )
        ;; Validate bundle
        (asserts! (get is-active bundle-data) err-invalid-input)
        
        ;; Purchase using batch function
        (try! (purchase-batch creator 
                             (get content-ids bundle-data) 
                             (get bundle-price bundle-data)))
        
        (ok true)
    )
)

;; Gift content access
(define-public (gift-content (recipient principal) 
                            (creator principal) 
                            (content-id uint) 
                            (price uint))
    (let (
        (gifter tx-sender)
        (gift-count (default-to u0 (map-get? user-gift-count gifter)))
        (gift-id (+ gift-count u1))
    )
        ;; Validate inputs
        (asserts! (not (is-eq gifter recipient)) err-invalid-input)
        (asserts! (and (>= price min-content-price) (<= price max-content-price)) err-invalid-input)
        
        ;; Process payment
        (let ((payment-result (try! (process-payment price creator))))
            
            ;; Create gift record
            (map-set gift-access {gifter: gifter, recipient: recipient, gift-id: gift-id} {
                creator: creator,
                content-id: content-id,
                amount: price,
                gifted-at: stacks-block-height,
                is-claimed: false
            })
            
            ;; Update gift count
            (map-set user-gift-count gifter gift-id)
            
            ;; Update stats
            (update-creator-payment-stats creator (get creator-amount payment-result) u1)
            (var-set total-volume (+ (var-get total-volume) price))
            
            (ok gift-id)
        )
    )
)

;; Claim gifted content
(define-public (claim-gift (gifter principal) (gift-id uint))
    (let (
        (recipient tx-sender)
        (gift-key {gifter: gifter, recipient: recipient, gift-id: gift-id})
        (gift-data (unwrap! (map-get? gift-access gift-key) err-not-found))
    )
        ;; Validate gift
        (asserts! (not (get is-claimed gift-data)) err-already-accessed)
        
        ;; Generate access token for recipient
        (let (
            (token-id (var-get next-access-token-id))
            (access-key-hash (generate-access-key recipient (get content-id gift-data) stacks-block-height))
            (access-key {purchaser: recipient, creator: (get creator gift-data), content-id: (get content-id gift-data)})
        )
            (map-set access-tokens token-id {
                purchaser: recipient,
                creator: (get creator gift-data),
                content-id: (get content-id gift-data),
                purchase-block: stacks-block-height,
                expiry-block: (+ stacks-block-height access-token-validity),
                amount-paid: (get amount gift-data),
                is-active: true,
                access-key: access-key-hash
            })
            
            ;; Map user to token
            (map-set user-access-tokens access-key token-id)
            
            ;; Mark gift as claimed
            (map-set gift-access gift-key
                (merge gift-data {is-claimed: true})
            )
            
            ;; Increment token ID
            (var-set next-access-token-id (+ token-id u1))
            
            (ok token-id)
        )
    )
)

;; Verify access token
(define-public (verify-access (token-id uint))
    (let (
        (token-data (unwrap! (map-get? access-tokens token-id) err-not-found))
    )
        ;; Check if token is valid
        (asserts! (get is-active token-data) err-expired)
        (asserts! (<= stacks-block-height (get expiry-block token-data)) err-expired)
        (asserts! (is-eq tx-sender (get purchaser token-data)) err-unauthorized)
        
        (ok true)
    )
)

;; Revoke access token (creator or admin only)
(define-public (revoke-access (token-id uint))
    (let (
        (token-data (unwrap! (map-get? access-tokens token-id) err-not-found))
    )
        ;; Only creator or contract owner can revoke
        (asserts! (or (is-eq tx-sender (get creator token-data)) 
                     (is-eq tx-sender contract-owner)) 
                 err-unauthorized)
        
        (map-set access-tokens token-id
            (merge token-data {is-active: false})
        )
        
        (ok true)
    )
)

;; Read-only Functions

(define-read-only (get-access-token (token-id uint))
    (ok (map-get? access-tokens token-id))
)

(define-read-only (get-user-access-token (purchaser principal) (creator principal) (content-id uint))
    (match (map-get? user-access-tokens {purchaser: purchaser, creator: creator, content-id: content-id})
        token-id (ok (map-get? access-tokens token-id))
        (ok none)
    )
)

(define-read-only (has-valid-access (purchaser principal) (creator principal) (content-id uint))
    (match (map-get? user-access-tokens {purchaser: purchaser, creator: creator, content-id: content-id})
        token-id 
            (match (map-get? access-tokens token-id)
                token-data (ok (and 
                    (get is-active token-data)
                    (<= stacks-block-height (get expiry-block token-data))
                ))
                (ok false)
            )
        (ok false)
    )
)

(define-read-only (get-transaction (transaction-id uint))
    (ok (map-get? transactions transaction-id))
)

(define-read-only (get-creator-payment-stats (creator principal))
    (ok (map-get? creator-payment-stats creator))
)

(define-read-only (get-bundle (creator principal) (bundle-id uint))
    (ok (map-get? content-bundles {creator: creator, bundle-id: bundle-id}))
)

(define-read-only (get-gift (gifter principal) (recipient principal) (gift-id uint))
    (ok (map-get? gift-access {gifter: gifter, recipient: recipient, gift-id: gift-id}))
)

(define-read-only (get-total-transactions)
    (ok (var-get total-transactions))
)

(define-read-only (get-total-volume)
    (ok (var-get total-volume))
)

(define-read-only (get-user-transaction-count (user principal))
    (ok (default-to u0 (map-get? user-transaction-count user)))
)

(define-read-only (calculate-batch-price (item-count uint) (base-price uint))
    (ok (apply-batch-discount (* base-price item-count) item-count))
)

;; Admin Functions

(define-public (set-platform-treasury (new-treasury principal))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-owner-only)
        (var-set platform-treasury new-treasury)
        (ok true)
    )
)

(define-public (deactivate-bundle (creator principal) (bundle-id uint))
    (let (
        (bundle-key {creator: creator, bundle-id: bundle-id})
        (bundle-data (unwrap! (map-get? content-bundles bundle-key) err-not-found))
    )
        (asserts! (is-eq tx-sender contract-owner) err-owner-only)
        
        (map-set content-bundles bundle-key
            (merge bundle-data {is-active: false})
        )
        
        (ok true)
    )
)

Functions (27)

FunctionAccessArgs
calculate-feeprivateamount: uint
calculate-creator-amountprivateamount: uint, fee: uint
apply-batch-discountprivatetotal-amount: uint, item-count: uint
generate-access-keyprivatepurchaser: principal, content-id: uint, block: uint
update-creator-payment-statsprivatecreator: principal, amount: uint, content-count: uint
process-paymentprivateamount: uint, creator: principal
purchase-contentpubliccreator: principal, content-id: uint, price: uint
purchase-batchpubliccreator: principal, content-ids: (list 10 uint
create-bundlepubliccontent-ids: (list 10 uint
purchase-bundlepubliccreator: principal, bundle-id: uint
gift-contentpublicrecipient: principal, creator: principal, content-id: uint, price: uint
claim-giftpublicgifter: principal, gift-id: uint
verify-accesspublictoken-id: uint
revoke-accesspublictoken-id: uint
get-access-tokenread-onlytoken-id: uint
get-user-access-tokenread-onlypurchaser: principal, creator: principal, content-id: uint
has-valid-accessread-onlypurchaser: principal, creator: principal, content-id: uint
get-transactionread-onlytransaction-id: uint
get-creator-payment-statsread-onlycreator: principal
get-bundleread-onlycreator: principal, bundle-id: uint
get-giftread-onlygifter: principal, recipient: principal, gift-id: uint
get-total-transactionsread-only
get-total-volumeread-only
get-user-transaction-countread-onlyuser: principal
calculate-batch-priceread-onlyitem-count: uint, base-price: uint
set-platform-treasurypublicnew-treasury: principal
deactivate-bundlepubliccreator: principal, bundle-id: uint