ETH Price: $2,441.46 (+3.69%)

Contract Diff Checker

Contract Name:
LayerrRenderer

Contract Source Code:

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;

import {ILayerrRenderer} from "./interfaces/ILayerrRenderer.sol";
import {IOwnable} from "./interfaces/IOwnable.sol";

/**
 * @title LayerrRenderer
 * @author 0xth0mas (Layerr)
 * @notice LayerrRenderer handles contractURI and tokenURI generation for contracts
 *         deployed on the Layerr platform. Contract owners have complete control of 
 *         their tokens with the ability to set their own renderer, host their tokens
 *         with Layerr, set all tokens to a prereveal image, or set a base URI that
 *         token ids will be appended to.
 *         Tokens hosted with Layerr will automatically generate a tokenURI with the
 *         `layerrBaseTokenUri`/{chainId}/{contractAddress}/{tokenId} to allow new tokens
 *         to be minted without updating a base uri.
 *         For long term storage, Layerr-hosted tokens can be swept onto Layerr's IPFS
 *         solution.
 */
contract LayerrRenderer is ILayerrRenderer {

    /// @dev Layerr-owned EOA that is allowed to update the base token and base contract URIs for Layerr-hosted non-IPFS tokens
    address public owner;

    /// @dev Layerr's signature account for checking parameters of tokens swept to Layerr IPFS
    address public layerrSigner = 0xc44355A57368C22D01A8a146C0a2669B504B25A0;

    /// @dev The base token URI that chainId, contractAddress and tokenId are added to for rendering
    string public layerrBaseTokenUri = 'https://metadata.layerr.art';

    /// @dev The base contract URI that chainId and contractAddress are added to for rendering
    string public layerrBaseContractUri = 'https://contract-metadata.layerr.art';

    /// @dev The rendering type for a token contract, defaults to LAYERR_HOSTED
    mapping(address => RenderType) public contractRenderType;

    /// @dev Base token URI set by the token contract owner for BASE_PLUS_TOKEN render type and LAYERR_HOSTED tokens on IPFS
    mapping(address => string) public contractBaseTokenUri;

    /// @dev Token contract URI set by the token contract owner
    mapping(address => string) public contractContractUri;

    /// @dev mapping of token contract addresses that flag a token contract as having all of its tokens hosted on Layerr IPFS
    mapping(address => bool) public layerrHostedAllTokensOnIPFS;

    /// @dev bitmap of token ids for a token contract that have been moved to Layerr hosted IPFS
    mapping(address => mapping(uint256 => uint256)) public layerrHostedTokensOnIPFS;

    /// @dev mapping of token contract addresses with the UTC timestamp of when the IPFS hosting is paid through
    mapping(address => uint256) public layerrHostedIPFSExpiration;

    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert NotContractOwner();
        }
        _;
    }

    constructor() {
        owner = 0x0000000000799dfE79Ed462822EC68eF9a6199e6;
    }

    /**
     * @inheritdoc ILayerrRenderer
     */
    function tokenURI(
        address contractAddress,
        uint256 tokenId
    ) external view returns (string memory _tokenURI) {
        RenderType renderType = contractRenderType[contractAddress];
        if (renderType == RenderType.LAYERR_HOSTED) {
            if(_isLayerrHostedIPFS(contractAddress, tokenId)) {
                _tokenURI = string(
                    abi.encodePacked(contractBaseTokenUri[contractAddress], _toString(tokenId))
                );
            } else {
                _tokenURI = string(
                    abi.encodePacked(
                        layerrBaseTokenUri,
                        "/",
                        _toString(block.chainid),
                        "/",
                        _toString(contractAddress),
                        "/",
                        _toString(tokenId)
                    )
                );
            }
        } else if (renderType == RenderType.PREREVEAL) {
            _tokenURI = contractBaseTokenUri[contractAddress];
        } else if (renderType == RenderType.BASE_PLUS_TOKEN) {
            _tokenURI = string(
                abi.encodePacked(contractBaseTokenUri[contractAddress], _toString(tokenId))
            );
        }
    }

    /**
     * @inheritdoc ILayerrRenderer
     */
    function contractURI(
        address contractAddress
    ) external view returns (string memory _contractURI) {
        _contractURI = contractContractUri[contractAddress];
        if (bytes(_contractURI).length == 0) {
            _contractURI = string(
                abi.encodePacked(
                    layerrBaseContractUri,
                    "/",
                    _toString(block.chainid),
                    "/",
                    _toString(contractAddress)
                )
            );
        }
    }

    /**
     * @notice Updates rendering settings for a contract. Must be the ERC173 owner for the token contract to call.
     * @param contractAddress address of the contract to set the base token URI for
     * @param baseTokenUri base token URI to set for `contractAddress`
     * @param renderType sets the current render type for the contract
     */
    function setContractBaseTokenUri(
        address contractAddress,
        string calldata baseTokenUri,
        RenderType renderType
    ) external {
        if (IOwnable(contractAddress).owner() != msg.sender) {
            revert NotContractOwner();
        }
        contractBaseTokenUri[contractAddress] = baseTokenUri;
        contractRenderType[contractAddress] = renderType;
    }

    /**
     * @notice Updates rendering settings for a contract. Must be the ERC173 owner for the token contract to call.
     * @param contractAddress address of the contract to set the base token URI for
     * @param contractUri contract URI to set for `contractAddress`
     */
    function setContractUri(
        address contractAddress,
        string calldata contractUri
    ) external {
        if (IOwnable(contractAddress).owner() != msg.sender) {
            revert NotContractOwner();
        }
        contractContractUri[contractAddress] = contractUri;
    }

    /**
     * @notice Updates the base token URI to sweep tokens to IPFS for Layerr hosted tokens
     *         This allows new tokens to continue to be minted on the contract with the default
     *         rendering address while existing tokens are moved onto IPFS for long term storage.
     * @param contractAddress address of the token contract
     * @param baseTokenUri base token URI to set for the contract's tokens
     * @param allTokens set to true for larger collections that are done minting
     *                  avoids setting each token id in the bitmap for gas savings but new tokens
     *                  will not render with the default rendering address
     * @param tokenIds array of token ids that are being swept to Layerr hosted IPFS
     * @param ipfsExpiration UTC timestamp that the IPFS hosting is paid through
     * @param signature signature by Layerr account to confirm the parameters supplied
     */
    function setContractBaseTokenUriForLayerrHostedIPFS(
        address contractAddress,
        string calldata baseTokenUri,
        bool allTokens,
        uint256[] calldata tokenIds,
        uint256 ipfsExpiration,
        bytes calldata signature
    ) external payable {
        if (IOwnable(contractAddress).owner() != msg.sender) {
            revert NotContractOwner();
        }

        bytes32 hash = keccak256(abi.encodePacked(contractAddress, baseTokenUri, ipfsExpiration, msg.value));
        address signer = _recover(hash, signature);
        if(signer != layerrSigner) revert InvalidSignature();

        (bool sent, ) = payable(owner).call{value: msg.value}("");
        if (!sent) {
            revert PaymentFailed();
        }

        layerrHostedIPFSExpiration[contractAddress] = ipfsExpiration;
        layerrHostedAllTokensOnIPFS[contractAddress] = allTokens;
        contractBaseTokenUri[contractAddress] = baseTokenUri;
        contractRenderType[contractAddress] = RenderType.LAYERR_HOSTED;

        for(uint256 i = 0;i < tokenIds.length;) {
            _setLayerrHostedIPFS(contractAddress, tokenIds[i]);
            unchecked { ++i; }
        }
    }

    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(ILayerrRenderer).interfaceId;
    }

    /* OWNER FUNCTIONS */
    
    /**
     * @notice Owner function to set the Layerr signature account
     * @param _layerrSigner address that will be used to check signatures
     */
    function setLayerrSigner(
        address _layerrSigner
    ) external onlyOwner {
        layerrSigner = _layerrSigner;
    }

    /**
     * @notice Owner function to set the default token rendering URI
     * @param _layerrBaseTokenUri base token uri to be used for default token rendering
     */
    function setLayerrBaseTokenUri(
        string calldata _layerrBaseTokenUri
    ) external onlyOwner {
        layerrBaseTokenUri = _layerrBaseTokenUri;
    }

    /**
     * @notice Owner function to set the default contract rendering URI
     * @param _layerrBaseContractUri base contract uri to be used for default rendering
     */

    function setLayerrBaseContractUri(
        string calldata _layerrBaseContractUri
    ) external onlyOwner {
        layerrBaseContractUri = _layerrBaseContractUri;
    }

    /* INTERNAL FUNCTIONS */

    /**
     * @notice Checks to see if a token has been flagged as being hosted on Layerr IPFS
     * @param contractAddress token contract address to check
     * @param tokenId id of the token to check
     * @return isIPFS if token has been flagged as being hosted on Layerr IPFS
     */
    function _isLayerrHostedIPFS(address contractAddress, uint256 tokenId) internal view returns(bool isIPFS) {
        isIPFS = layerrHostedAllTokensOnIPFS[contractAddress] || layerrHostedTokensOnIPFS[contractAddress][tokenId >> 8] & (1 << (tokenId & 0xFF)) != 0;
    }

    /**
     * @notice Flags a token as being hosted on Layerr IPFS in a bitmap
     * @param contractAddress token contract address
     * @param tokenId id of the token
     */
    function _setLayerrHostedIPFS(address contractAddress, uint256 tokenId) internal {
        layerrHostedTokensOnIPFS[contractAddress][tokenId >> 8] |= (1 << (tokenId & 0xFF));
    }

    function _toString(uint256 value) internal pure virtual returns (string memory str) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := add(mload(0x40), 0xa0)
            mstore(0x40, m)
            str := sub(m, 0x20)
            mstore(str, 0)
            let end := str

            for { let temp := value } 1 {} {
                str := sub(str, 1)
                mstore8(str, add(48, mod(temp, 10)))
                temp := div(temp, 10)
                if iszero(temp) { break }
            }

            let length := sub(end, str)
            str := sub(str, 0x20)
            mstore(str, length)
        }
    }

    function _toString(address value) internal pure returns (string memory) {
        bytes memory s = new bytes(40);
        for (uint i = 0; i < 20; ) {
            bytes1 b = bytes1(uint8(uint(uint160(value)) / (2**(8*(19 - i)))));
            bytes1 hi = bytes1(uint8(b) / 16);
            bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi));
            s[2*i] = _char(hi);
            s[2*i+1] = _char(lo);
            unchecked { ++i; }
        }
        return string(s);
    }

    function _char(bytes1 b) internal pure returns (bytes1 c) {
        if (uint8(b) < 10) return bytes1(uint8(b) + 0x30);
        else return bytes1(uint8(b) + 0x57);
    }

    /**
     * @dev Recover signer address from a message by using their signature
     * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address.
     * @param sig bytes signature, the signature is generated using web3.eth.sign()
     */
    function _recover(
        bytes32 hash,
        bytes calldata sig
    ) internal pure returns (address) {
        bytes32 r;
        bytes32 s;
        uint8 v;

        // Check the signature length
        if (sig.length != 65) {
            return (address(0));
        }

        // Divide the signature in r, s and v variables
        /// @solidity memory-safe-assembly
        assembly {
            r := calldataload(sig.offset)
            s := calldataload(add(sig.offset, 32))
            v := byte(0, calldataload(add(sig.offset, 64)))
        }

        // If the version is correct return the signer address
        if (v != 27 && v != 28) {
            return (address(0));
        } else {
            return ecrecover(hash, v, r, s);
        }
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC165} from "./IERC165.sol";

/**
 * @title ILayerrRenderer
 * @author 0xth0mas (Layerr)
 * @notice ILayerrRenderer interface defines functions required in LayerrRenderer to be callable by token contracts
 */
interface ILayerrRenderer is ERC165 {

    enum RenderType {
        LAYERR_HOSTED,
        PREREVEAL,
        BASE_PLUS_TOKEN
    }

    /// @dev Thrown when a payment fails for Layerr-hosted IPFS
    error PaymentFailed();

    /// @dev Thrown when a call is made for an owner-function by a non-contract owner
    error NotContractOwner();

    /// @dev Thrown when a signature is not made by the authorized account
    error InvalidSignature();

    /**
     * @notice Generates a tokenURI for the `contractAddress` and `tokenId`
     * @param contractAddress token contract address to render a token URI for
     * @param tokenId token id to render
     * @return uri for the token metadata
     */
    function tokenURI(
        address contractAddress,
        uint256 tokenId
    ) external view returns (string memory);

    /**
     * @notice Generates a contractURI for the `contractAddress`
     * @param contractAddress contract address to render a contract URI for
     * @return uri for the contract metadata
     */
    function contractURI(
        address contractAddress
    ) external view returns (string memory);
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. 
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC165} from './IERC165.sol';

interface IOwnable is ERC165 {

    /// @dev Thrown when a non-owner is attempting to perform an owner function
    error NotContractOwner();

    /// @dev Emitted when contract ownership is transferred to a new owner
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /// @notice Get the address of the owner    
    /// @return The address of the owner.
    function owner() view external returns(address);
	
    /// @notice Set the address of the new owner of the contract
    /// @dev Set _newOwner to address(0) to renounce any ownership.
    /// @param _newOwner The address of the new owner of the contract    
    function transferOwnership(address _newOwner) external;	
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):