Source Code

;; Gaga Finance Royalty Engine
;; Manages royalty configurations and calculates payouts for NFT sales
;; Fully independent and reusable across multiple marketplaces

;; ============================================
;; CONSTANTS - Error Codes
;; ============================================
(define-constant ERR-NOT-AUTHORIZED (err u300))
(define-constant ERR-NOT-FOUND (err u301))
(define-constant ERR-ALREADY-EXISTS (err u302))
(define-constant ERR-INVALID-PERCENTAGE (err u303))
(define-constant ERR-INVALID-RECIPIENT (err u304))
(define-constant ERR-CONTRACT-PAUSED (err u305))
(define-constant ERR-ROYALTY-TOO-HIGH (err u306))

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

;; Maximum royalty: 25% (2500 basis points)
(define-constant MAX-ROYALTY-BPS u2500)

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

;; ============================================
;; DATA VARIABLES
;; ============================================
(define-data-var is-paused bool false)

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

;; Royalty configuration per collection
;; nft-contract -> { recipient, royalty-bps, admin }
(define-map collection-royalties 
  principal 
  { 
    recipient: principal,
    royalty-bps: uint,
    admin: principal
  }
)

;; Secondary recipients for split royalties (optional)
(define-map secondary-recipients
  { nft-contract: principal, index: uint }
  { recipient: principal, share-bps: uint }
)

;; Number of secondary recipients per collection
(define-map secondary-recipient-count principal uint)

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

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

(define-private (is-collection-admin (nft-contract principal))
  (match (map-get? collection-royalties nft-contract)
    config (or 
      (is-eq tx-sender (get admin config))
      (is-contract-owner)
    )
    (is-contract-owner)
  )
)

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

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

;; Get royalty configuration for a collection
(define-read-only (get-royalty-info (nft-contract principal))
  (ok (map-get? collection-royalties nft-contract))
)

;; Get royalty percentage in basis points
(define-read-only (get-royalty-bps (nft-contract principal))
  (ok (default-to u0 
    (match (map-get? collection-royalties nft-contract)
      config (some (get royalty-bps config))
      none
    )
  ))
)

;; Get royalty recipient
(define-read-only (get-royalty-recipient (nft-contract principal))
  (ok (match (map-get? collection-royalties nft-contract)
    config (some (get recipient config))
    none
  ))
)

;; Calculate royalty amount for a sale
(define-read-only (calculate-royalty (nft-contract principal) (sale-price uint))
  (let (
    (royalty-bps (default-to u0 
      (match (map-get? collection-royalties nft-contract)
        config (some (get royalty-bps config))
        none
      )
    ))
  )
    (ok (/ (* sale-price royalty-bps) BPS-DENOMINATOR))
  )
)

;; Get full payout breakdown for a sale
;; Returns: { royalty-amount, royalty-recipient, seller-amount }
(define-read-only (get-payout-breakdown (nft-contract principal) (sale-price uint) (protocol-fee-bps uint))
  (let (
    (royalty-bps (default-to u0 
      (match (map-get? collection-royalties nft-contract)
        config (some (get royalty-bps config))
        none
      )
    ))
    (royalty-amount (/ (* sale-price royalty-bps) BPS-DENOMINATOR))
    (protocol-fee (/ (* sale-price protocol-fee-bps) BPS-DENOMINATOR))
    (seller-amount (- (- sale-price royalty-amount) protocol-fee))
    (recipient (match (map-get? collection-royalties nft-contract)
      config (get recipient config)
      CONTRACT-OWNER ;; fallback
    ))
  )
    (ok {
      royalty-amount: royalty-amount,
      royalty-recipient: recipient,
      protocol-fee: protocol-fee,
      seller-amount: seller-amount
    })
  )
)

;; Check if collection has royalty configured
(define-read-only (has-royalty (nft-contract principal))
  (is-some (map-get? collection-royalties nft-contract))
)

;; Get maximum allowed royalty
(define-read-only (get-max-royalty-bps)
  (ok MAX-ROYALTY-BPS)
)

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

;; Get secondary recipient count
(define-read-only (get-secondary-recipient-count (nft-contract principal))
  (ok (default-to u0 (map-get? secondary-recipient-count nft-contract)))
)

;; Get secondary recipient at index
(define-read-only (get-secondary-recipient (nft-contract principal) (index uint))
  (ok (map-get? secondary-recipients { nft-contract: nft-contract, index: index }))
)

;; ============================================
;; PUBLIC FUNCTIONS - ROYALTY CONFIGURATION
;; ============================================

;; Register royalty for a new collection
(define-public (register-collection (nft-contract principal) (recipient principal) (royalty-bps uint))
  (begin
    (try! (assert-not-paused))
    
    ;; Only contract owner can register new collections
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    
    ;; Validate royalty is within limits
    (asserts! (<= royalty-bps MAX-ROYALTY-BPS) ERR-ROYALTY-TOO-HIGH)
    
    ;; Check collection not already registered
    (asserts! (not (has-royalty nft-contract)) ERR-ALREADY-EXISTS)
    
    ;; Set royalty configuration
    (map-set collection-royalties nft-contract {
      recipient: recipient,
      royalty-bps: royalty-bps,
      admin: tx-sender
    })
    
    (print { 
      event: "collection-registered",
      nft-contract: nft-contract,
      recipient: recipient,
      royalty-bps: royalty-bps
    })
    
    (ok true)
  )
)

;; Update royalty percentage (collection admin only)
(define-public (update-royalty-bps (nft-contract principal) (new-royalty-bps uint))
  (begin
    (try! (assert-not-paused))
    
    ;; Check caller is collection admin
    (asserts! (is-collection-admin nft-contract) ERR-NOT-AUTHORIZED)
    
    ;; Validate new royalty
    (asserts! (<= new-royalty-bps MAX-ROYALTY-BPS) ERR-ROYALTY-TOO-HIGH)
    
    ;; Get existing config
    (let (
      (config (unwrap! (map-get? collection-royalties nft-contract) ERR-NOT-FOUND))
    )
      ;; Update only the royalty
      (map-set collection-royalties nft-contract 
        (merge config { royalty-bps: new-royalty-bps })
      )
      
      (print { 
        event: "royalty-updated",
        nft-contract: nft-contract,
        old-royalty-bps: (get royalty-bps config),
        new-royalty-bps: new-royalty-bps
      })
      
      (ok true)
    )
  )
)

;; Update royalty recipient (collection admin only)
(define-public (update-royalty-recipient (nft-contract principal) (new-recipient principal))
  (begin
    (try! (assert-not-paused))
    
    (asserts! (is-collection-admin nft-contract) ERR-NOT-AUTHORIZED)
    
    (let (
      (config (unwrap! (map-get? collection-royalties nft-contract) ERR-NOT-FOUND))
    )
      (map-set collection-royalties nft-contract 
        (merge config { recipient: new-recipient })
      )
      
      (print { 
        event: "recipient-updated",
        nft-contract: nft-contract,
        old-recipient: (get recipient config),
        new-recipient: new-recipient
      })
      
      (ok true)
    )
  )
)

;; Transfer collection admin (current admin only)
(define-public (transfer-admin (nft-contract principal) (new-admin principal))
  (begin
    (try! (assert-not-paused))
    
    (asserts! (is-collection-admin nft-contract) ERR-NOT-AUTHORIZED)
    
    (let (
      (config (unwrap! (map-get? collection-royalties nft-contract) ERR-NOT-FOUND))
    )
      (map-set collection-royalties nft-contract 
        (merge config { admin: new-admin })
      )
      
      (print { 
        event: "admin-transferred",
        nft-contract: nft-contract,
        old-admin: (get admin config),
        new-admin: new-admin
      })
      
      (ok true)
    )
  )
)

;; ============================================
;; SECONDARY RECIPIENTS (SPLIT ROYALTIES)
;; ============================================

;; Add secondary recipient for split royalties
(define-public (add-secondary-recipient (nft-contract principal) (recipient principal) (share-bps uint))
  (begin
    (try! (assert-not-paused))
    (asserts! (is-collection-admin nft-contract) ERR-NOT-AUTHORIZED)
    
    (let (
      (current-count (default-to u0 (map-get? secondary-recipient-count nft-contract)))
      (new-index current-count)
    )
      ;; Max 5 secondary recipients
      (asserts! (< current-count u5) ERR-INVALID-PERCENTAGE)
      
      ;; Add recipient
      (map-set secondary-recipients 
        { nft-contract: nft-contract, index: new-index }
        { recipient: recipient, share-bps: share-bps }
      )
      
      ;; Increment count
      (map-set secondary-recipient-count nft-contract (+ current-count u1))
      
      (print { 
        event: "secondary-recipient-added",
        nft-contract: nft-contract,
        index: new-index,
        recipient: recipient,
        share-bps: share-bps
      })
      
      (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)
  )
)

;; Remove collection royalty configuration (owner only)
(define-public (remove-collection (nft-contract principal))
  (begin
    (asserts! (is-contract-owner) ERR-NOT-AUTHORIZED)
    
    (map-delete collection-royalties nft-contract)
    
    (print { 
      event: "collection-removed",
      nft-contract: nft-contract
    })
    
    (ok true)
  )
)

Functions (20)

FunctionAccessArgs
set-pausedpublicpaused: bool
is-contract-ownerprivate
is-collection-adminprivatenft-contract: principal
assert-not-pausedprivate
get-royalty-inforead-onlynft-contract: principal
get-royalty-bpsread-onlynft-contract: principal
get-royalty-recipientread-onlynft-contract: principal
calculate-royaltyread-onlynft-contract: principal, sale-price: uint
get-payout-breakdownread-onlynft-contract: principal, sale-price: uint, protocol-fee-bps: uint
has-royaltyread-onlynft-contract: principal
get-max-royalty-bpsread-only
is-contract-pausedread-only
get-secondary-recipient-countread-onlynft-contract: principal
get-secondary-recipientread-onlynft-contract: principal, index: uint
register-collectionpublicnft-contract: principal, recipient: principal, royalty-bps: uint
update-royalty-bpspublicnft-contract: principal, new-royalty-bps: uint
update-royalty-recipientpublicnft-contract: principal, new-recipient: principal
transfer-adminpublicnft-contract: principal, new-admin: principal
add-secondary-recipientpublicnft-contract: principal, recipient: principal, share-bps: uint
remove-collectionpublicnft-contract: principal