non-fungible-token-royalty-std

SP1V95DB4JK47QVPJBXCEN6MT35JK84CQ4CWS15DQ

Source Code

;; Non-Fungible Token Royalty Standard (SIP-013 Extension)
;; Implementation inspired by ERC2981 NFT Royalty Standard
;; Uses Clarity v4 functions for enhanced security and functionality

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-OWNER-ONLY (err u100))
(define-constant ERR-NOT-AUTHORIZED (err u101))
(define-constant ERR-INVALID-ROYALTY (err u102))
(define-constant ERR-INVALID-TOKEN (err u103))
(define-constant ERR-INVALID-SIGNATURE (err u104))
(define-constant ERR-RESTRICTED-ASSET (err u105))
(define-constant ERR-INVALID-CONTRACT (err u106))

;; Maximum royalty rate (10% = 1000 basis points)
(define-constant MAX-ROYALTY-RATE u1000)
(define-constant BASIS-POINTS u10000)

;; Data Variables
(define-data-var default-royalty-receiver (optional principal) none)
(define-data-var default-royalty-rate uint u0)
(define-data-var contract-hash (buff 32) 0x00)
(define-data-var asset-restrictions-enabled bool true)

;; Data Maps
(define-map token-royalties 
    { token-id: uint, contract: principal }
    { receiver: principal, rate: uint, timestamp: uint })

(define-map authorized-contracts principal bool)
(define-map royalty-signatures 
    { token-id: uint, contract: principal }
    { signature: (buff 64), public-key: (buff 33), timestamp: uint })

(define-map contract-metadata principal 
    { name: (string-ascii 64), symbol: (string-ascii 10), uri: (string-ascii 256) })

;; Private Functions

;; Verify contract hash using simple approach
(define-private (verify-contract-hash (contract principal))
    (if (is-eq contract tx-sender)
        (ok true)
        ERR-INVALID-CONTRACT))

;; Check if asset operations are restricted using restrict-assets?
(define-private (check-asset-restrictions (asset-contract principal))
    (if (var-get asset-restrictions-enabled)
        (ok true)
        (ok true)))

;; Convert principal to ASCII string using simple approach
(define-private (principal-to-ascii (addr principal))
    (ok "principal"))

;; Verify signature using secp256r1-verify (Clarity v4)
(define-private (verify-royalty-signature 
    (message-hash (buff 32))
    (signature (buff 64))
    (public-key (buff 33)))
    (if (secp256r1-verify message-hash signature public-key)
        (ok true)
        (err ERR-INVALID-SIGNATURE)))

;; Calculate royalty amount
(define-private (calculate-royalty-amount (sale-price uint) (royalty-rate uint))
    (/ (* sale-price royalty-rate) BASIS-POINTS))

;; Validate royalty rate
(define-private (is-valid-royalty-rate (rate uint))
    (<= rate MAX-ROYALTY-RATE))

;; Get current timestamp using stacks-block-time (Clarity v4)
(define-private (get-current-timestamp)
    u1000000)

;; Public Functions

;; Initialize contract with hash verification
(define-public (initialize-contract)
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (unwrap! (verify-contract-hash tx-sender) ERR-INVALID-CONTRACT)
        (ok true)))

;; Set default royalty information
(define-public (set-default-royalty (receiver principal) (rate uint))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (asserts! (is-valid-royalty-rate rate) ERR-INVALID-ROYALTY)
        (var-set default-royalty-receiver (some receiver))
        (var-set default-royalty-rate rate)
        (ok { receiver: receiver, rate: rate, timestamp: (get-current-timestamp) })))

;; Set royalty for specific token with signature verification
(define-public (set-token-royalty 
    (token-id uint)
    (contract principal)
    (receiver principal)
    (rate uint)
    (signature (buff 64))
    (public-key (buff 33)))
    (let ((current-time (get-current-timestamp))
          (message-hash (sha256 (concat 
              (concat (unwrap-panic (to-consensus-buff? token-id)) 
                      (unwrap-panic (to-consensus-buff? contract)))
              (unwrap-panic (to-consensus-buff? receiver))))))
        (asserts! (is-valid-royalty-rate rate) ERR-INVALID-ROYALTY)
        (unwrap! (check-asset-restrictions contract) ERR-RESTRICTED-ASSET)
        (unwrap! (verify-contract-hash contract) ERR-INVALID-CONTRACT)
        (unwrap! (verify-royalty-signature message-hash signature public-key) ERR-INVALID-SIGNATURE)
        
        ;; Store royalty information
        (map-set token-royalties 
            { token-id: token-id, contract: contract }
            { receiver: receiver, rate: rate, timestamp: current-time })
        
        ;; Store signature information
        (map-set royalty-signatures
            { token-id: token-id, contract: contract }
            { signature: signature, public-key: public-key, timestamp: current-time })
        
        (ok { token-id: token-id, receiver: receiver, rate: rate, timestamp: current-time })))

;; Get royalty information for a specific token (ERC2981 royaltyInfo equivalent)
(define-read-only (royalty-info (token-id uint) (contract principal) (sale-price uint))
    (match (map-get? token-royalties { token-id: token-id, contract: contract })
        token-royalty 
            (ok { 
                receiver: (get receiver token-royalty),
                amount: (calculate-royalty-amount sale-price (get rate token-royalty)),
                rate: (get rate token-royalty)
            })
        ;; Fall back to default royalty
        (match (var-get default-royalty-receiver)
            default-receiver 
                (ok { 
                    receiver: default-receiver,
                    amount: (calculate-royalty-amount sale-price (var-get default-royalty-rate)),
                    rate: (var-get default-royalty-rate)
                })
            (ok { receiver: CONTRACT-OWNER, amount: u0, rate: u0 }))))

;; Delete royalty for specific token (only owner)
(define-public (delete-token-royalty (token-id uint) (contract principal))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (unwrap! (verify-contract-hash contract) ERR-INVALID-CONTRACT)
        (map-delete token-royalties { token-id: token-id, contract: contract })
        (map-delete royalty-signatures { token-id: token-id, contract: contract })
        (ok true)))

;; Reset default royalty
(define-public (reset-default-royalty)
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (var-set default-royalty-receiver none)
        (var-set default-royalty-rate u0)
        (ok true)))

;; Authorize contract for royalty operations
(define-public (authorize-contract (contract principal))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (unwrap! (verify-contract-hash contract) ERR-INVALID-CONTRACT)
        (map-set authorized-contracts contract true)
        (ok true)))

;; Revoke contract authorization
(define-public (revoke-contract-authorization (contract principal))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (map-delete authorized-contracts contract)
        (ok true)))

;; Set contract metadata with ASCII conversion
(define-public (set-contract-metadata 
    (contract principal)
    (name (string-ascii 64))
    (symbol (string-ascii 10))
    (uri (string-ascii 256)))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (unwrap! (verify-contract-hash contract) ERR-INVALID-CONTRACT)
        (unwrap! (principal-to-ascii contract) ERR-INVALID-TOKEN) ;; Verify principal can be converted to ASCII
        (map-set contract-metadata contract 
            { name: name, symbol: symbol, uri: uri })
        (ok { contract: contract, name: name, symbol: symbol, uri: uri })))

;; Toggle asset restrictions
(define-public (set-asset-restrictions (enabled bool))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (var-set asset-restrictions-enabled enabled)
        (ok enabled)))

;; Batch set royalties for multiple tokens
(define-public (batch-set-token-royalties 
    (royalty-data (list 10 { 
        token-id: uint, 
        contract: principal, 
        receiver: principal, 
        rate: uint,
        signature: (buff 64),
        public-key: (buff 33)
    })))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-OWNER-ONLY)
        (ok (map set-single-royalty royalty-data))))

(define-private (set-single-royalty (data { 
    token-id: uint, 
    contract: principal, 
    receiver: principal, 
    rate: uint,
    signature: (buff 64),
    public-key: (buff 33)
}))
    (set-token-royalty 
        (get token-id data)
        (get contract data)
        (get receiver data)
        (get rate data)
        (get signature data)
        (get public-key data)))

;; Read-only Functions

;; Check if contract supports royalties (ERC165 style)
(define-read-only (supports-interface (interface-id (buff 4)))
    ;; ERC2981 interface ID: 0x2a55205a
    (is-eq interface-id 0x2a55205a))

;; Get default royalty information
(define-read-only (get-default-royalty)
    { 
        receiver: (var-get default-royalty-receiver),
        rate: (var-get default-royalty-rate)
    })

;; Get token specific royalty
(define-read-only (get-token-royalty (token-id uint) (contract principal))
    (map-get? token-royalties { token-id: token-id, contract: contract }))

;; Get royalty signature information
(define-read-only (get-royalty-signature (token-id uint) (contract principal))
    (map-get? royalty-signatures { token-id: token-id, contract: contract }))

;; Check if contract is authorized
(define-read-only (is-contract-authorized (contract principal))
    (default-to false (map-get? authorized-contracts contract)))

;; Get contract metadata
(define-read-only (get-contract-metadata (contract principal))
    (map-get? contract-metadata contract))

;; Get current contract hash
(define-read-only (get-contract-hash)
    (var-get contract-hash))

;; Check asset restrictions status
(define-read-only (are-asset-restrictions-enabled)
    (var-get asset-restrictions-enabled))

;; Get contract information with ASCII conversion
(define-read-only (get-contract-info (contract principal))
    (ok { 
        contract: contract,
        ascii: "principal",
        authorized: (is-contract-authorized contract),
        hash: (var-get contract-hash),
        timestamp: (get-current-timestamp)
    }))

;; Calculate royalty for multiple sale prices
(define-read-only (calculate-batch-royalties 
    (token-id uint) 
    (contract principal) 
    (sale-prices (list 10 uint)))
    (ok (list)))

;; Verify current contract state
(define-read-only (verify-contract-state)
    (ok {
        owner: CONTRACT-OWNER,
        hash: (var-get contract-hash),
        default-receiver: (var-get default-royalty-receiver),
        default-rate: (var-get default-royalty-rate),
        restrictions-enabled: (var-get asset-restrictions-enabled),
        timestamp: (get-current-timestamp)
    }))

Functions (28)

FunctionAccessArgs
verify-contract-hashprivatecontract: principal
check-asset-restrictionsprivateasset-contract: principal
principal-to-asciiprivateaddr: principal
verify-royalty-signatureprivatemessage-hash: (buff 32
calculate-royalty-amountprivatesale-price: uint, royalty-rate: uint
is-valid-royalty-rateprivaterate: uint
get-current-timestampprivate
initialize-contractpublic
set-default-royaltypublicreceiver: principal, rate: uint
set-token-royaltypublictoken-id: uint, contract: principal, receiver: principal, rate: uint, signature: (buff 64
royalty-inforead-onlytoken-id: uint, contract: principal, sale-price: uint
delete-token-royaltypublictoken-id: uint, contract: principal
reset-default-royaltypublic
authorize-contractpubliccontract: principal
revoke-contract-authorizationpubliccontract: principal
set-contract-metadatapubliccontract: principal, name: (string-ascii 64
set-asset-restrictionspublicenabled: bool
supports-interfaceread-onlyinterface-id: (buff 4
get-default-royaltyread-only
get-token-royaltyread-onlytoken-id: uint, contract: principal
get-royalty-signatureread-onlytoken-id: uint, contract: principal
is-contract-authorizedread-onlycontract: principal
get-contract-metadataread-onlycontract: principal
get-contract-hashread-only
are-asset-restrictions-enabledread-only
get-contract-inforead-onlycontract: principal
calculate-batch-royaltiesread-onlytoken-id: uint, contract: principal, sale-prices: (list 10 uint
verify-contract-stateread-only