Transient Labs Developer Docs
  • 👋Welcome!
  • 📑TL Creator Contracts
    • Creator Contracts Overview
    • Implementation Contracts
      • v3.x.x
      • v2.x.x
      • v1.x.x
    • Smart Contract Documentation
    • Common Features
      • Access Control
      • Royalties
      • Story Inscriptions
      • NFT Delegation
      • BlockList
    • ERC721TL
    • ERC1155TL
    • Shatter
    • ERC7160TL
    • Doppelganger
    • Collector's Choice
  • ⛔BlockList
    • BlockList Overview
    • Implementation Contracts
    • Deployments
    • Smart Contract Documentation
  • 🔖Story Inscriptions
    • Story Inscriptions Overview
    • Story Inscription Format
    • Smart Contract Documentation
  • 🖼️T.R.A.C.E.
    • T.R.A.C.E. Overview
    • Record Schema
    • Implementation
    • Smart Contract Documentation
      • TRACE
      • TRACERSRegistry
  • 🥞Stacks
    • Stacks Overview
    • Deployments
    • Smart Contract Documentation
  • 🎨Dynamic Art
    • Dynamic Art Overview
    • How to Create
    • How to Display
  • 🔗Integrations
    • Deploying TL Contracts
    • NFT Delegation
    • Integrating with Marketplaces
    • Metadata Structure
    • Inheriting TL Contracts
    • Onchain Art
    • Embeddable Components
  • ❔Miscellaneous
    • Supported Blockchains
    • tl-sol-tools
    • Licensing
    • Batch Upload Secret JSON
Powered by GitBook
On this page
  1. TL Creator Contracts
  2. Common Features

Royalties

TL Creator Contracts utilize EIP-2981 to configure royalties on-chain.

PreviousAccess ControlNextStory Inscriptions

Last updated 1 year ago

All TL Creator Contracts implement compliant royalties. The implementation we created allows for a default royalty configuration set at the contract level and then individual token overrides.

Only the owner of the contract can edit the royalty configuration either at the contract or token level.

It is important to note that individual token royalties are respected at marketplaces like SuperRare and Foundation, but NOT respected by OpenSea at this time. We have asked them about it and it is on the roadmap, but until then, the default royalty configuration is used.

The code utilized by the Creator Contracts can be found below.

📑
EIP-2981
Page cover image
https://github.com/Transient-Labs/tl-sol-tools/blob/main/src/upgradeable/royalties/EIP2981TLUpgradeable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC165Upgradeable} from "@openzeppelin-contracts-upgradeable-5.0.2/utils/introspection/ERC165Upgradeable.sol";
import {IEIP2981} from "../../royalties/IEIP2981.sol";

/// @title EIP2981TLUpgradeable.sol
/// @notice Abstract contract to define a default royalty spec
///         while allowing for specific token overrides
/// @dev Follows EIP-2981 (https://eips.ethereum.org/EIPS/eip-2981)
/// @author transientlabs.xyz
/// @custom:version 3.1.0
abstract contract EIP2981TLUpgradeable is IEIP2981, ERC165Upgradeable {
    /*//////////////////////////////////////////////////////////////////////////
                                    Types
    //////////////////////////////////////////////////////////////////////////*/

    struct RoyaltySpec {
        address recipient;
        uint256 percentage;
    }

    /*//////////////////////////////////////////////////////////////////////////
                                    Storage
    //////////////////////////////////////////////////////////////////////////*/

    /// @custom:storage-location erc7201:transientlabs.storage.EIP2981TLStorage
    struct EIP2981TLStorage {
        address defaultRecipient;
        uint256 defaultPercentage;
        mapping(uint256 => RoyaltySpec) tokenOverrides;
    }

    // keccak256(abi.encode(uint256(keccak256("transientlabs.storage.EIP2981TLStorage")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant EIP2981TLStorageLocation =
        0xe9db8e9b56f2e28e12956850f386d9a4c1e886a4f584b61a10a9d0cacee70700;

    function _getEIP2981TLStorage() private pure returns (EIP2981TLStorage storage $) {
        assembly {
            $.slot := EIP2981TLStorageLocation
        }
    }

    /*//////////////////////////////////////////////////////////////////////////
                                Constants
    //////////////////////////////////////////////////////////////////////////*/

    uint256 public constant BASIS = 10_000;

    /*//////////////////////////////////////////////////////////////////////////
                                    Events
    //////////////////////////////////////////////////////////////////////////*/

    /// @dev Event to emit when the default roylaty is updated
    event DefaultRoyaltyUpdate(address indexed sender, address newRecipient, uint256 newPercentage);

    /// @dev Event to emit when a token royalty is overriden
    event TokenRoyaltyOverride(
        address indexed sender, uint256 indexed tokenId, address newRecipient, uint256 newPercentage
    );

    /*//////////////////////////////////////////////////////////////////////////
                                    Errors
    //////////////////////////////////////////////////////////////////////////*/

    /// @dev error if the recipient is set to address(0)
    error ZeroAddressError();

    /// @dev error if the royalty percentage is greater than to 100%
    error MaxRoyaltyError();

    /*//////////////////////////////////////////////////////////////////////////
                                Initializer
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Function to initialize the contract
    /// @param defaultRecipient The default royalty payout address
    /// @param defaultPercentage The deafult royalty percentage, out of 10,000
    function __EIP2981TL_init(address defaultRecipient, uint256 defaultPercentage) internal onlyInitializing {
        __EIP2981TL_init_unchained(defaultRecipient, defaultPercentage);
    }

    /// @notice Unchained function to initialize the contract
    /// @param defaultRecipient The default royalty payout address
    /// @param defaultPercentage The deafult royalty percentage, out of 10,000
    function __EIP2981TL_init_unchained(address defaultRecipient, uint256 defaultPercentage)
        internal
        onlyInitializing
    {
        _setDefaultRoyaltyInfo(defaultRecipient, defaultPercentage);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                Royalty Changing Functions
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Function to set default royalty info
    /// @param newRecipient The new default royalty payout address
    /// @param newPercentage The new default royalty percentage, out of 10,000
    function _setDefaultRoyaltyInfo(address newRecipient, uint256 newPercentage) internal {
        EIP2981TLStorage storage $ = _getEIP2981TLStorage();
        if (newRecipient == address(0)) revert ZeroAddressError();
        if (newPercentage > 10_000) revert MaxRoyaltyError();
        $.defaultRecipient = newRecipient;
        $.defaultPercentage = newPercentage;
        emit DefaultRoyaltyUpdate(msg.sender, newRecipient, newPercentage);
    }

    /// @notice Function to override royalty spec on a specific token
    /// @param tokenId The token id to override royalty for
    /// @param newRecipient The new royalty payout address
    /// @param newPercentage The new royalty percentage, out of 10,000
    function _overrideTokenRoyaltyInfo(uint256 tokenId, address newRecipient, uint256 newPercentage) internal {
        EIP2981TLStorage storage $ = _getEIP2981TLStorage();
        if (newRecipient == address(0)) revert ZeroAddressError();
        if (newPercentage > 10_000) revert MaxRoyaltyError();
        $.tokenOverrides[tokenId].recipient = newRecipient;
        $.tokenOverrides[tokenId].percentage = newPercentage;
        emit TokenRoyaltyOverride(msg.sender, tokenId, newRecipient, newPercentage);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                Royalty Info
    //////////////////////////////////////////////////////////////////////////*/

    /// @inheritdoc IEIP2981
    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount)
    {
        EIP2981TLStorage storage $ = _getEIP2981TLStorage();
        address recipient = $.defaultRecipient;
        uint256 percentage = $.defaultPercentage;
        if ($.tokenOverrides[tokenId].recipient != address(0)) {
            recipient = $.tokenOverrides[tokenId].recipient;
            percentage = $.tokenOverrides[tokenId].percentage;
        }
        return (recipient, salePrice * percentage / BASIS);
    }

    /*//////////////////////////////////////////////////////////////////////////
                                ERC-165 Override
    //////////////////////////////////////////////////////////////////////////*/

    /// @inheritdoc ERC165Upgradeable
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable) returns (bool) {
        return interfaceId == type(IEIP2981).interfaceId || ERC165Upgradeable.supportsInterface(interfaceId);
    }

    /*//////////////////////////////////////////////////////////////////////////
                            External View Functions
    //////////////////////////////////////////////////////////////////////////*/

    /// @notice Query the default royalty receiver and percentage.
    /// @return Tuple containing the default royalty recipient and percentage out of 10_000
    function getDefaultRoyaltyRecipientAndPercentage() external view returns (address, uint256) {
        EIP2981TLStorage storage $ = _getEIP2981TLStorage();
        return ($.defaultRecipient, $.defaultPercentage);
    }
}