ETH Price: $2,548.27 (-2.67%)

Transaction Decoder

Block:
22554073 at May-24-2025 05:04:59 PM +UTC
Transaction Fee:
0.000025375546979664 ETH $0.06
Gas Used:
32,102 Gas / 0.790466232 Gwei

Emitted Events:

326 GnarsHD.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000fe473552f6bdb509c9b3f927205eef07bffd19eb, 0x0000000000000000000000000000000000000000000000000000000000000dd9 )

Account State Difference:

  Address   Before After State Difference Code
0x0A8A8c28...0D6AF17Ac
0.010708012529955093 Eth
Nonce: 2
0.010682636982975429 Eth
Nonce: 3
0.000025375546979664
(Titan Builder)
11.184242749283102451 Eth11.184245707819601859 Eth0.000002958536499408

Execution Trace

GnarsHD.1a4d3535( )
  • SkateContractV2.ownerOf( tokenId=3545 ) => ( 0xfE473552F6bdb509C9B3F927205EEF07bFfD19Eb )
    File 1 of 2: GnarsHD
    // SPDX-License-Identifier: CC0-1.0
    pragma solidity 0.8.21;
    /// @title Gnars HD
    /// @notice High definition Gnars counterparts
    /// @author Volky
    /// @dev This contract describes Gnars HD as 1:1 counterparts for each GnarV2. They are not mintable, since the ownership of the respective GnarV2 is mirrored on the GnarHD.
    ///
    ////////////////////////////////////////////////////////////////////////////////////////////
    //                                                                                        //
    //                                                                                        //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀       ▀▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀   ▄▄░░░░▄   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀▀       ▀▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀  ▄▒░░░░░░░░░▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀  ▄▐▒░░░░   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀  ▀░▐░░░░░░░░▌▐  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▄▒░░░░░░░▐   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▌░ ░░░░░░░░░░▓  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▄░░░░░░░░░▌▌  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▄▒ ▒░░░░░░░░░░▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▐░ ░░░░░░▐▐▌▌  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓   ░ ▐░░░░░░░░░░▓   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▌ ▐░░░░░░░░░▓   ▓▓▓▓▓▓▓▓▓▓▓▓   ▓▒ ░░░░░░░░░░▐   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▒ ░░░░░░░░░░▐   ▓▓▓▓▓▓▓▓▓▓▓▓   ▌░▒░░░░░░░░░░▌  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▒ ░░░░░░░░░░▐   ▓▓▀▀▀         ▐░ ░░░░░░░░░░▐   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▒ ▐░░░░░░░░░░        ▄▄▄▄▄▄▄▄▄▐░░░░░░░░░░░░▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▌░▐░░░░░░░░░░▌   ▐░ ░░░░░░░░░░▌░░░░░░░░░░░▐  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▐░ ▒░░░░░░░░░░▄▄░░░░░░░░░░░░░░▌░░░░░░░░░░▌▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▌▒░░░░░░░░░░░▓░░░░░░░░░░░░░░░▌░░░░░░░░░░▒▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▒░░░░░░░░░░░▌░░░░░▄▄▀▀▀▀▀▀▀▀▀▒░░░░░░░░░▀▄   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▐░░░░░░░░░░░▌░░░░▀░░░░░░░░░░░░░░░░░░░░░░░░▌  ▀▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▐░░░░░░░░░░▒░░░▌▒▒░░░░░░░░░░░░░░░░░░░░░░░░▀  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▐░░░░░░░░░▒░░░▌▄▐░░░░░░░░░░░░░░░░░░░░░░░░▒▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▒░░░░░░░░▓░░░░▓░▄░▀▒░░░░░░░░░░░░░░░░░░░▒▐▐  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ░░░░░░░░░░▌░░░░░▀▀▒▄▄▄▄▄▄▄▓▀▀▒░░░░░░░░░░░▐  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▐░░░░░░░░░░░▒▀▄▄░▄▄░▀░░░░░░░░░░░░░░░░░░░░░▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▐░░░░░░░░░░░░░░░░▓░░░░░░░░░░░░░░░░░░░░░░░▌  ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▒░░░░░░░░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░░▀  ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓  ▐░░░░░░░░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░▄▀  ▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌   ▒░░░░░░░░░░░░░▀▒░░░░░░░░░░░░░░░░░▒▀  ▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   █▀▀▄▄▄░░░░░░░░░▀▄░░░░░░░░░░░▄▄█   ▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▌  ▐░░░░░░░░░░░░░░░░░▒▀▀▀▀▀▀▒░░░░▐   ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓   ▀░░░░░░░░░░░░░░░░░░░░░░░░░▄▀  ▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄   ▀▀▒▄░░░░░░░░░░░░░░░▒▀▀   ▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄                     ▄▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄▄▄▄▄▄▄▄▄▄▄▄▄▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //    ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓    //
    //                                                                                        //
    //              ░██████╗░███╗░░██╗░█████╗░██████╗░░██████╗  ██╗░░██╗██████╗░              //
    //              ██╔════╝░████╗░██║██╔══██╗██╔══██╗██╔════╝  ██║░░██║██╔══██╗              //
    //              ██║░░██╗░██╔██╗██║███████║██████╔╝╚█████╗░  ███████║██║░░██║              //
    //              ██║░░╚██╗██║╚████║██╔══██║██╔══██╗░╚═══██╗  ██╔══██║██║░░██║              //
    //              ╚██████╔╝██║░╚███║██║░░██║██║░░██║██████╔╝  ██║░░██║██████╔╝              //
    //              ░╚═════╝░╚═╝░░╚══╝╚═╝░░╚═╝╚═╝░░╚═╝╚═════╝░  ╚═╝░░╚═╝╚═════╝░              //
    //                                                                                        //
    //                                                                                        //
    ////////////////////////////////////////////////////////////////////////////////////////////
    import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol";
    import {Owned} from "solmate/auth/Owned.sol";
    import {Base64} from "base64/base64.sol";
    contract GnarsHD is Owned {
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                             STRUCTS / EVENTS / ERRORS
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        struct Artwork {
            string ipfsFolder;
            uint48 amountBackgrounds;
            uint48 amountBodies;
            uint48 amountAccessories;
            uint48 amountHeads;
            uint48 amountNoggles;
        }
        event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
        error Untransferable();
        error TokenDoesNotExist(uint256 tokenId);
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                             STORAGE
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        string public name = "Gnars HD";
        string public symbol = "GNARSHD";
        string public rendererBaseUri;
        string public contractURI;
        Artwork public artwork;
        ISkateContractV2 public gnarsV2;
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                            CONSTRUCTOR
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        constructor(
            address _gnarsV2Address,
            string memory _rendererBaseUri,
            Artwork memory _artwork,
            string memory _contractURI,
            address _owner
        ) Owned(_owner) {
            gnarsV2 = ISkateContractV2(_gnarsV2Address);
            rendererBaseUri = _rendererBaseUri;
            artwork = _artwork;
            contractURI = _contractURI;
        }
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                             MAIN LOGIC
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        function setArtwork(Artwork memory _artwork) public onlyOwner {
            artwork = _artwork;
        }
        function setContractUri(string memory _contractURI) public onlyOwner {
            contractURI = _contractURI;
        }
        function setRendererBaseUri(string memory _rendererBaseUri) public onlyOwner {
            rendererBaseUri = _rendererBaseUri;
        }
        /// @notice The properties and query string for a generated token
        /// @param _tokenId The ERC-721 token id
        function getAttributes(uint256 _tokenId)
            public
            view
            returns (string memory resultAttributes, string memory queryString)
        {
            (uint48 background, uint48 body, uint48 accessory, uint48 head, uint48 glasses) = gnarsV2.seeds(_tokenId);
            IGnarDescriptorV2 descriptor = IGnarDescriptorV2(gnarsV2.descriptor());
            IGnarDecorator decorator = IGnarDecorator(descriptor.decorator());
            queryString = string.concat(
                "?contractAddress=",
                Strings.toHexString(address(this)),
                "&tokenId=",
                Strings.toString(_tokenId),
                getBackgroundQueryParam(background),
                getPartQueryParam("BODY", body, artwork.amountBodies),
                getPartQueryParam("ACCESSORY", accessory, artwork.amountAccessories),
                getPartQueryParam("HEADS", head, artwork.amountHeads),
                getPartQueryParam("NOGGLES", glasses, artwork.amountNoggles)
            );
            resultAttributes = string.concat(
                getPartTrait("Background", background, decorator.backgrounds),
                ",",
                getPartTrait("Body", body, decorator.bodies),
                ",",
                getPartTrait("Accessory", accessory, decorator.accessories),
                ",",
                getPartTrait("Head", head, decorator.heads),
                ",",
                getPartTrait("Glasses", glasses, decorator.glasses)
            );
        }
        function getPartQueryParam(string memory folder, uint48 partIndex, uint48 amountOfPart)
            public
            view
            returns (string memory)
        {
            if (partIndex >= amountOfPart) {
                return string.concat("&images=", artwork.ipfsFolder, "/", folder, "/FALLBACK.PNG");
            }
            return string.concat("&images=", artwork.ipfsFolder, "/", folder, "/", Strings.toString(partIndex), ".PNG");
        }
        function getBackgroundQueryParam(uint48 backgroundIndex) public view returns (string memory) {
            if (backgroundIndex >= artwork.amountBackgrounds) {
                return string.concat("&images=", artwork.ipfsFolder, "/BACKGROUND/FALLBACK.PNG");
            }
            return string.concat("&images=", artwork.ipfsFolder, "/BACKGROUND/", Strings.toString(backgroundIndex), ".PNG");
        }
        function getPartTrait(
            string memory traitType,
            uint48 partIndex,
            function (uint256) external view returns (string memory) getPartDescription
        ) public view returns (string memory) {
            try getPartDescription(partIndex) returns (string memory partDescription) {
                return string.concat('{"trait_type":"', traitType, '","value":"', partDescription, '"}');
            } catch {
                return string.concat('{"trait_type":"', traitType, '","value":"Unknown"}');
            }
        }
        function tokenURI(uint256 _tokenId) public view returns (string memory) {
            if (gnarsV2.ownerOf(_tokenId) == address(0)) {
                revert TokenDoesNotExist(_tokenId);
            }
            (string memory attributes, string memory queryString) = getAttributes(_tokenId);
            return string(
                abi.encodePacked(
                    "data:application/json;base64,",
                    Base64.encode(
                        bytes(
                            abi.encodePacked(
                                '{"name":"Gnar HD #',
                                Strings.toString(_tokenId),
                                '", "description":"High definition Gnar #',
                                Strings.toString(_tokenId),
                                " counterpart",
                                '", "attributes": [',
                                attributes,
                                '], "image": "',
                                string.concat(rendererBaseUri, queryString),
                                '"}'
                            )
                        )
                    )
                )
            );
        }
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                                  PASSTHROUGH METHODS
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        /// @notice Returns the total amount of Gnars HD in existence
        /// @dev Delegates to the Gnars V2 contract
        function totalSupply() external view returns (uint256) {
            return gnarsV2.totalSupply();
        }
        /// @notice Returns the tokenId of the Gnar HD by index
        /// @dev Delegates to the Gnars V2 contract
        function tokenByIndex(uint256 _index) external view returns (uint256) {
            return gnarsV2.tokenByIndex(_index);
        }
        /// @notice Returns the Gnar HD owner's address by token index
        /// @dev Delegates to the Gnars V2 contract
        function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256) {
            return gnarsV2.tokenOfOwnerByIndex(_owner, _index);
        }
        /// @notice Returns the Gnar HD owner's address by token id
        /// @dev Delegates to the Gnars V2 contract
        function ownerOf(uint256 id) public view returns (address owner) {
            return gnarsV2.ownerOf(id);
        }
        /// @notice Returns the amount of Gnars HD owned by the specified address
        /// @dev Delegates to the Gnars V2 contract
        function balanceOf(address owner) public view returns (uint256) {
            return gnarsV2.balanceOf(owner);
        }
        /// @notice Refresh ownership of specified tokens on marketplaces/datasets that are showing out of date information
        /// @dev Since this token is not mintable, there's no Transfer event. This method emits the Transfer event so that consumers that can detect the creation/new ownership of the token.
        /// @param tokenIds The ids of tokens to refresh
        function assertOwnership(uint256[] memory tokenIds) public {
            for (uint256 i = 0; i < tokenIds.length; i++) {
                uint256 tokenId = tokenIds[i];
                emit Transfer(address(0), gnarsV2.ownerOf(tokenId), tokenId);
            }
        }
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                                  ERC721 LOGIC
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        /// @notice Gnars HD are not transferable
        /// @dev Will always revert
        function approve(address, uint256) public pure {
            revert Untransferable();
        }
        /// @notice Gnars HD are not transferable
        /// @dev Will always revert
        function setApprovalForAll(address, bool) public pure {
            revert Untransferable();
        }
        /// @notice Gnars HD are not transferable
        /// @dev Will always revert
        function transferFrom(address, address, uint256) public pure {
            revert Untransferable();
        }
        /// @notice Gnars HD are not transferable
        /// @dev Will always revert
        function safeTransferFrom(address, address, uint256) public pure {
            revert Untransferable();
        }
        /// @notice Gnars HD are not transferable
        /// @dev Will always revert
        function safeTransferFrom(address, address, uint256, bytes calldata) public pure {
            revert Untransferable();
        }
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                                  ERC6454 LOGIC
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        /// @notice Gnars HD are not transferable
        /// @dev Will always return false
        function isTransferable(uint256, address, address) external pure returns (bool) {
            return false;
        }
        /* ⌐◨—————————————————————————————————————————————————————————————◨
                                  ERC165 LOGIC
           ⌐◨—————————————————————————————————————————————————————————————◨ */
        function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
            return interfaceId == 0x01ffc9a7 // ERC165 Interface ID for ERC165
                || interfaceId == 0x80ac58cd // ERC165 Interface ID for ERC721
                || interfaceId == 0x5b5e139f // ERC165 Interface ID for ERC721Metadata
                || interfaceId == 0x780e9d63 // ERC165 Interface ID for ERC721Enumerable
                || interfaceId == 0x7f5828d0 // ERC165 Interface ID for ERC173
                || interfaceId == 0x91a6262f; // ERC165 Interface ID for ERC6454
        }
    }
    interface IGnarDecorator {
        function accessories(uint256) external view returns (string memory);
        function backgrounds(uint256) external view returns (string memory);
        function bodies(uint256) external view returns (string memory);
        function glasses(uint256) external view returns (string memory);
        function heads(uint256) external view returns (string memory);
    }
    interface IGnarDescriptorV2 {
        function decorator() external view returns (address);
    }
    interface ISkateContractV2 {
        function balanceOf(address owner) external view returns (uint256);
        function descriptor() external view returns (address);
        function ownerOf(uint256 tokenId) external view returns (address);
        function seeds(uint256)
            external
            view
            returns (uint48 background, uint48 body, uint48 accessory, uint48 head, uint48 glasses);
        function tokenByIndex(uint256 index) external view returns (uint256);
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
        function totalSupply() external view returns (uint256);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)
    pragma solidity ^0.8.0;
    import "./math/Math.sol";
    import "./math/SignedMath.sol";
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _SYMBOLS = "0123456789abcdef";
        uint8 private constant _ADDRESS_LENGTH = 20;
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            unchecked {
                uint256 length = Math.log10(value) + 1;
                string memory buffer = new string(length);
                uint256 ptr;
                /// @solidity memory-safe-assembly
                assembly {
                    ptr := add(buffer, add(32, length))
                }
                while (true) {
                    ptr--;
                    /// @solidity memory-safe-assembly
                    assembly {
                        mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                    }
                    value /= 10;
                    if (value == 0) break;
                }
                return buffer;
            }
        }
        /**
         * @dev Converts a `int256` to its ASCII `string` decimal representation.
         */
        function toString(int256 value) internal pure returns (string memory) {
            return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            unchecked {
                return toHexString(value, Math.log256(value) + 1);
            }
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
        /**
         * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
         */
        function toHexString(address addr) internal pure returns (string memory) {
            return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
        }
        /**
         * @dev Returns true if the two strings are equal.
         */
        function equal(string memory a, string memory b) internal pure returns (bool) {
            return keccak256(bytes(a)) == keccak256(bytes(b));
        }
    }
    // SPDX-License-Identifier: AGPL-3.0-only
    pragma solidity >=0.8.0;
    /// @notice Simple single owner authorization mixin.
    /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
    abstract contract Owned {
        /*//////////////////////////////////////////////////////////////
                                     EVENTS
        //////////////////////////////////////////////////////////////*/
        event OwnershipTransferred(address indexed user, address indexed newOwner);
        /*//////////////////////////////////////////////////////////////
                                OWNERSHIP STORAGE
        //////////////////////////////////////////////////////////////*/
        address public owner;
        modifier onlyOwner() virtual {
            require(msg.sender == owner, "UNAUTHORIZED");
            _;
        }
        /*//////////////////////////////////////////////////////////////
                                   CONSTRUCTOR
        //////////////////////////////////////////////////////////////*/
        constructor(address _owner) {
            owner = _owner;
            emit OwnershipTransferred(address(0), _owner);
        }
        /*//////////////////////////////////////////////////////////////
                                 OWNERSHIP LOGIC
        //////////////////////////////////////////////////////////////*/
        function transferOwnership(address newOwner) public virtual onlyOwner {
            owner = newOwner;
            emit OwnershipTransferred(msg.sender, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    pragma solidity >=0.6.0;
    /// @title Base64
    /// @author Brecht Devos - <[email protected]>
    /// @notice Provides functions for encoding/decoding base64
    library Base64 {
        string internal constant TABLE_ENCODE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
        bytes  internal constant TABLE_DECODE = hex"0000000000000000000000000000000000000000000000000000000000000000"
                                                hex"00000000000000000000003e0000003f3435363738393a3b3c3d000000000000"
                                                hex"00000102030405060708090a0b0c0d0e0f101112131415161718190000000000"
                                                hex"001a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132330000000000";
        function encode(bytes memory data) internal pure returns (string memory) {
            if (data.length == 0) return '';
            // load the table into memory
            string memory table = TABLE_ENCODE;
            // multiply by 4/3 rounded up
            uint256 encodedLen = 4 * ((data.length + 2) / 3);
            // add some extra buffer at the end required for the writing
            string memory result = new string(encodedLen + 32);
            assembly {
                // set the actual output length
                mstore(result, encodedLen)
                // prepare the lookup table
                let tablePtr := add(table, 1)
                // input ptr
                let dataPtr := data
                let endPtr := add(dataPtr, mload(data))
                // result ptr, jump over length
                let resultPtr := add(result, 32)
                // run over the input, 3 bytes at a time
                for {} lt(dataPtr, endPtr) {}
                {
                    // read 3 bytes
                    dataPtr := add(dataPtr, 3)
                    let input := mload(dataPtr)
                    // write 4 characters
                    mstore8(resultPtr, mload(add(tablePtr, and(shr(18, input), 0x3F))))
                    resultPtr := add(resultPtr, 1)
                    mstore8(resultPtr, mload(add(tablePtr, and(shr(12, input), 0x3F))))
                    resultPtr := add(resultPtr, 1)
                    mstore8(resultPtr, mload(add(tablePtr, and(shr( 6, input), 0x3F))))
                    resultPtr := add(resultPtr, 1)
                    mstore8(resultPtr, mload(add(tablePtr, and(        input,  0x3F))))
                    resultPtr := add(resultPtr, 1)
                }
                // padding with '='
                switch mod(mload(data), 3)
                case 1 { mstore(sub(resultPtr, 2), shl(240, 0x3d3d)) }
                case 2 { mstore(sub(resultPtr, 1), shl(248, 0x3d)) }
            }
            return result;
        }
        function decode(string memory _data) internal pure returns (bytes memory) {
            bytes memory data = bytes(_data);
            if (data.length == 0) return new bytes(0);
            require(data.length % 4 == 0, "invalid base64 decoder input");
            // load the table into memory
            bytes memory table = TABLE_DECODE;
            // every 4 characters represent 3 bytes
            uint256 decodedLen = (data.length / 4) * 3;
            // add some extra buffer at the end required for the writing
            bytes memory result = new bytes(decodedLen + 32);
            assembly {
                // padding with '='
                let lastBytes := mload(add(data, mload(data)))
                if eq(and(lastBytes, 0xFF), 0x3d) {
                    decodedLen := sub(decodedLen, 1)
                    if eq(and(lastBytes, 0xFFFF), 0x3d3d) {
                        decodedLen := sub(decodedLen, 1)
                    }
                }
                // set the actual output length
                mstore(result, decodedLen)
                // prepare the lookup table
                let tablePtr := add(table, 1)
                // input ptr
                let dataPtr := data
                let endPtr := add(dataPtr, mload(data))
                // result ptr, jump over length
                let resultPtr := add(result, 32)
                // run over the input, 4 characters at a time
                for {} lt(dataPtr, endPtr) {}
                {
                   // read 4 characters
                   dataPtr := add(dataPtr, 4)
                   let input := mload(dataPtr)
                   // write 3 bytes
                   let output := add(
                       add(
                           shl(18, and(mload(add(tablePtr, and(shr(24, input), 0xFF))), 0xFF)),
                           shl(12, and(mload(add(tablePtr, and(shr(16, input), 0xFF))), 0xFF))),
                       add(
                           shl( 6, and(mload(add(tablePtr, and(shr( 8, input), 0xFF))), 0xFF)),
                                   and(mload(add(tablePtr, and(        input , 0xFF))), 0xFF)
                        )
                    )
                    mstore(resultPtr, shl(232, output))
                    resultPtr := add(resultPtr, 3)
                }
            }
            return result;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Standard math utilities missing in the Solidity language.
     */
    library Math {
        enum Rounding {
            Down, // Toward negative infinity
            Up, // Toward infinity
            Zero // Toward zero
        }
        /**
         * @dev Returns the largest of two numbers.
         */
        function max(uint256 a, uint256 b) internal pure returns (uint256) {
            return a > b ? a : b;
        }
        /**
         * @dev Returns the smallest of two numbers.
         */
        function min(uint256 a, uint256 b) internal pure returns (uint256) {
            return a < b ? a : b;
        }
        /**
         * @dev Returns the average of two numbers. The result is rounded towards
         * zero.
         */
        function average(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b) / 2 can overflow.
            return (a & b) + (a ^ b) / 2;
        }
        /**
         * @dev Returns the ceiling of the division of two numbers.
         *
         * This differs from standard division with `/` in that it rounds up instead
         * of rounding down.
         */
        function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
            // (a + b - 1) / b can overflow on addition, so we distribute.
            return a == 0 ? 0 : (a - 1) / b + 1;
        }
        /**
         * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
         * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
         * with further edits by Uniswap Labs also under MIT license.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
            unchecked {
                // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
                // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
                // variables such that product = prod1 * 2^256 + prod0.
                uint256 prod0; // Least significant 256 bits of the product
                uint256 prod1; // Most significant 256 bits of the product
                assembly {
                    let mm := mulmod(x, y, not(0))
                    prod0 := mul(x, y)
                    prod1 := sub(sub(mm, prod0), lt(mm, prod0))
                }
                // Handle non-overflow cases, 256 by 256 division.
                if (prod1 == 0) {
                    // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                    // The surrounding unchecked block does not change this fact.
                    // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                    return prod0 / denominator;
                }
                // Make sure the result is less than 2^256. Also prevents denominator == 0.
                require(denominator > prod1, "Math: mulDiv overflow");
                ///////////////////////////////////////////////
                // 512 by 256 division.
                ///////////////////////////////////////////////
                // Make division exact by subtracting the remainder from [prod1 prod0].
                uint256 remainder;
                assembly {
                    // Compute remainder using mulmod.
                    remainder := mulmod(x, y, denominator)
                    // Subtract 256 bit number from 512 bit number.
                    prod1 := sub(prod1, gt(remainder, prod0))
                    prod0 := sub(prod0, remainder)
                }
                // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
                // See https://cs.stackexchange.com/q/138556/92363.
                // Does not overflow because the denominator cannot be zero at this stage in the function.
                uint256 twos = denominator & (~denominator + 1);
                assembly {
                    // Divide denominator by twos.
                    denominator := div(denominator, twos)
                    // Divide [prod1 prod0] by twos.
                    prod0 := div(prod0, twos)
                    // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                    twos := add(div(sub(0, twos), twos), 1)
                }
                // Shift in bits from prod1 into prod0.
                prod0 |= prod1 * twos;
                // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
                // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
                // four bits. That is, denominator * inv = 1 mod 2^4.
                uint256 inverse = (3 * denominator) ^ 2;
                // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
                // in modular arithmetic, doubling the correct bits in each step.
                inverse *= 2 - denominator * inverse; // inverse mod 2^8
                inverse *= 2 - denominator * inverse; // inverse mod 2^16
                inverse *= 2 - denominator * inverse; // inverse mod 2^32
                inverse *= 2 - denominator * inverse; // inverse mod 2^64
                inverse *= 2 - denominator * inverse; // inverse mod 2^128
                inverse *= 2 - denominator * inverse; // inverse mod 2^256
                // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
                // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
                // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
                // is no longer required.
                result = prod0 * inverse;
                return result;
            }
        }
        /**
         * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
         */
        function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
            uint256 result = mulDiv(x, y, denominator);
            if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
                result += 1;
            }
            return result;
        }
        /**
         * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
         *
         * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
         */
        function sqrt(uint256 a) internal pure returns (uint256) {
            if (a == 0) {
                return 0;
            }
            // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
            //
            // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
            // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
            //
            // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
            // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
            // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
            //
            // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
            uint256 result = 1 << (log2(a) >> 1);
            // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
            // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
            // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
            // into the expected uint128 result.
            unchecked {
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                result = (result + a / result) >> 1;
                return min(result, a / result);
            }
        }
        /**
         * @notice Calculates sqrt(a), following the selected rounding direction.
         */
        function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = sqrt(a);
                return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 2, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 128;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 64;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 32;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 16;
                }
                if (value >> 8 > 0) {
                    value >>= 8;
                    result += 8;
                }
                if (value >> 4 > 0) {
                    value >>= 4;
                    result += 4;
                }
                if (value >> 2 > 0) {
                    value >>= 2;
                    result += 2;
                }
                if (value >> 1 > 0) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log2(value);
                return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 10, rounded down, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >= 10 ** 64) {
                    value /= 10 ** 64;
                    result += 64;
                }
                if (value >= 10 ** 32) {
                    value /= 10 ** 32;
                    result += 32;
                }
                if (value >= 10 ** 16) {
                    value /= 10 ** 16;
                    result += 16;
                }
                if (value >= 10 ** 8) {
                    value /= 10 ** 8;
                    result += 8;
                }
                if (value >= 10 ** 4) {
                    value /= 10 ** 4;
                    result += 4;
                }
                if (value >= 10 ** 2) {
                    value /= 10 ** 2;
                    result += 2;
                }
                if (value >= 10 ** 1) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log10(value);
                return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
            }
        }
        /**
         * @dev Return the log in base 256, rounded down, of a positive value.
         * Returns 0 if given 0.
         *
         * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
         */
        function log256(uint256 value) internal pure returns (uint256) {
            uint256 result = 0;
            unchecked {
                if (value >> 128 > 0) {
                    value >>= 128;
                    result += 16;
                }
                if (value >> 64 > 0) {
                    value >>= 64;
                    result += 8;
                }
                if (value >> 32 > 0) {
                    value >>= 32;
                    result += 4;
                }
                if (value >> 16 > 0) {
                    value >>= 16;
                    result += 2;
                }
                if (value >> 8 > 0) {
                    result += 1;
                }
            }
            return result;
        }
        /**
         * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
         * Returns 0 if given 0.
         */
        function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
            unchecked {
                uint256 result = log256(value);
                return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Standard signed math utilities missing in the Solidity language.
     */
    library SignedMath {
        /**
         * @dev Returns the largest of two signed numbers.
         */
        function max(int256 a, int256 b) internal pure returns (int256) {
            return a > b ? a : b;
        }
        /**
         * @dev Returns the smallest of two signed numbers.
         */
        function min(int256 a, int256 b) internal pure returns (int256) {
            return a < b ? a : b;
        }
        /**
         * @dev Returns the average of two signed numbers without overflow.
         * The result is rounded towards zero.
         */
        function average(int256 a, int256 b) internal pure returns (int256) {
            // Formula from the book "Hacker's Delight"
            int256 x = (a & b) + ((a ^ b) >> 1);
            return x + (int256(uint256(x) >> 255) & (a ^ b));
        }
        /**
         * @dev Returns the absolute unsigned value of a signed value.
         */
        function abs(int256 n) internal pure returns (uint256) {
            unchecked {
                // must be unchecked in order to support `n = type(int256).min`
                return uint256(n >= 0 ? n : -n);
            }
        }
    }
    

    File 2 of 2: SkateContractV2
    // SPDX-License-Identifier: GPL-3.0
    /// @title The Gnars ERC-721 token
    // LICENSE
    // SkateContractV2.sol is a modified version of Nounders DAO's NounsToken.sol:
    // https://github.com/nounsDAO/nouns-monorepo/blob/master/packages/nouns-contracts/contracts/NounsToken.sol
    //
    // NounsToken.sol source code Copyright Nounders DAO licensed under the GPL-3.0 license.
    // With modifications by Gnars.
    pragma solidity 0.8.6;
    import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
    import {ERC721} from "./base/ERC721.sol";
    import {ERC721Checkpointable} from "./base/ERC721Checkpointable.sol";
    import {ISkateContractV2} from "../interfaces/ISkateContractV2.sol";
    import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
    import {IGnarSeederV2} from "../interfaces/IGNARSeederV2.sol";
    import {IGnarDescriptorV2} from "../interfaces/IGNARDescriptorV2.sol";
    import {IProxyRegistry} from "../interfaces/IProxyRegistry.sol";
    import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
    contract SkateContractV2 is ISkateContractV2, Ownable, ERC721Checkpointable {
        using Strings for uint256;
        // The nounders DAO address (creators org)
        address public noundersDAO;
        // An address who has permissions to mint Gnar
        address public minter;
        // The Gnar token URI descriptor
        IGnarDescriptorV2 public descriptor;
        // The Gnar token seeder
        IGnarSeederV2 public seeder;
        // Whether the minter can be updated
        bool public isMinterLocked;
        // Whether the descriptor can be updated
        bool public isDescriptorLocked;
        // Whether the seeder can be updated
        bool public isSeederLocked;
        // The Gnar seeds
        mapping(uint256 => IGnarSeederV2.Seed) public seeds;
        uint256 public initialGnarId;
        // The internal Gnar ID tracker
        uint256 private currentGnarId;
        // OpenSea's Proxy Registry
        IProxyRegistry public immutable proxyRegistry;
        // Store custom descriptions for Gnars
        mapping(uint256 => string) public customDescription;
        /**
         * @notice Require that the minter has not been locked.
         */
        modifier whenMinterNotLocked() {
            require(!isMinterLocked, "Minter is locked");
            _;
        }
        /**
         * @notice Require that the descriptor has not been locked.
         */
        modifier whenDescriptorNotLocked() {
            require(!isDescriptorLocked, "Descriptor is locked");
            _;
        }
        /**
         * @notice Require that the seeder has not been locked.
         */
        modifier whenSeederNotLocked() {
            require(!isSeederLocked, "Seeder is locked");
            _;
        }
        /**
         * @notice Require that the sender is the nounders DAO.
         */
        modifier onlyNoundersDAO() {
            require(msg.sender == noundersDAO, "Sender is not the nounders DAO");
            _;
        }
        /**
         * @notice Require that the sender is the minter.
         */
        modifier onlyMinter() {
            require(msg.sender == minter, "Sender is not the minter");
            _;
        }
        constructor(
            address _noundersDAO,
            address _minter,
            IGnarDescriptorV2 _descriptor,
            IGnarSeederV2 _seeder,
            IProxyRegistry _proxyRegistry,
            uint256 _initialGnarId
        ) ERC721("Gnars", "GNAR") {
            require(
                _noundersDAO != address(0) &&
                    _minter != address(0) &&
                    address(_descriptor) != address(0) &&
                    address(_seeder) != address(0) &&
                    address(_proxyRegistry) != address(0),
                "ZERO ADDRESS"
            );
            noundersDAO = _noundersDAO;
            minter = _minter;
            descriptor = _descriptor;
            seeder = _seeder;
            proxyRegistry = _proxyRegistry;
            initialGnarId = _initialGnarId;
            currentGnarId = _initialGnarId;
        }
        /**
         * @notice Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
         */
        function isApprovedForAll(address _owner, address operator) public view override(IERC721, ERC721) returns (bool) {
            // Whitelist OpenSea proxy contract for easy trading.
            if (proxyRegistry.proxies(_owner) == operator) {
                return true;
            }
            return super.isApprovedForAll(_owner, operator);
        }
        /**
         * @notice Mint a Gnar to the minter, along with a possible nounders reward
         * Noun. Nounders reward Gnars are minted every 10 Gnars, starting at 0.
         * @dev Call _mintTo with the to address(es).
         */
        function mint() public override onlyMinter returns (uint256) {
            if ((currentGnarId - initialGnarId) % 10 == 0) {
                _mintTo(noundersDAO, currentGnarId++);
            }
            return _mintTo(minter, currentGnarId++);
        }
        /**
         * @notice Burn a Gnar.
         */
        function burn(uint256 gnarId) public override onlyMinter {
            require(minter == ownerOf(gnarId), "Can burn its own token only");
            _burn(gnarId);
            emit GnarBurned(gnarId);
        }
        /**
         * @notice A distinct Uniform Resource Identifier (URI) for a given asset.
         * @dev See {IERC721Metadata-tokenURI}.
         */
        // function tokenURI(uint256 tokenId) public view override returns (string memory) {
        //     require(_exists(tokenId), "GnarToken: URI query for nonexistent token");
        //     return descriptor.tokenURI(tokenId, seeds[tokenId]);
        // }
        function tokenURI(uint256 tokenId) public view override returns (string memory) {
            require(_exists(tokenId), "GnarToken: URI query for nonexistent token");
            string memory gnarId = tokenId.toString();
            string memory name = string(abi.encodePacked("Gnar ", gnarId));
            string memory description = viewDescription(tokenId);
            return descriptor.genericDataURI(name, description, seeds[tokenId]);
        }
        /**
         * @notice Set the token minter.
         * @dev Only callable by the owner when not locked.
         */
        function setMinter(address _minter) external override onlyOwner whenMinterNotLocked {
            require(_minter != address(0), "ZERO ADDRESS");
            minter = _minter;
            emit MinterUpdated(_minter);
        }
        /**
         * @notice Lock the minter.
         * @dev This cannot be reversed and is only callable by the owner when not locked.
         */
        function lockMinter() external override onlyOwner whenMinterNotLocked {
            isMinterLocked = true;
            emit MinterLocked();
        }
        /**
         * @notice Set the token URI descriptor.
         * @dev Only callable by the owner when not locked.
         */
        function setDescriptor(IGnarDescriptorV2 _descriptor) external override onlyOwner whenDescriptorNotLocked {
            require(address(_descriptor) != address(0), "ZERO ADDRESS");
            descriptor = _descriptor;
            emit DescriptorUpdated(_descriptor);
        }
        /**
         * @notice Lock the descriptor.
         * @dev This cannot be reversed and is only callable by the owner when not locked.
         */
        function lockDescriptor() external override onlyOwner whenDescriptorNotLocked {
            isDescriptorLocked = true;
            emit DescriptorLocked();
        }
        /**
         * @notice Set the token seeder.
         * @dev Only callable by the owner when not locked.
         */
        function setSeeder(IGnarSeederV2 _seeder) external override onlyOwner whenSeederNotLocked {
            require(address(_seeder) != address(0), "ZERO ADDRESS");
            seeder = _seeder;
            emit SeederUpdated(_seeder);
        }
        /**
         * @notice Lock the seeder.
         * @dev This cannot be reversed and is only callable by the owner when not locked.
         */
        function lockSeeder() external override onlyOwner whenSeederNotLocked {
            isSeederLocked = true;
            emit SeederLocked();
        }
        /**
         * @notice Mint a Gnar with `gnarId` to the provided `to` address.
         */
        function _mintTo(address to, uint256 gnarId) internal returns (uint256) {
            IGnarSeederV2.Seed memory seed = seeds[gnarId] = seeder.generateSeed(gnarId, descriptor);
            _mint(owner(), to, gnarId);
            emit GnarCreated(gnarId, seed);
            return gnarId;
        }
        /**
         * @notice Set a custom description for a Gnar token on-chain that will display on OpenSea and other sites.
         * Takes the format of "Gnar [tokenId] is a [....]"
         * May be modified at any time
         * Send empty string to revert to default.
         * @dev Only callable by the holder of the token.
         */
        function setCustomDescription(uint256 tokenId, string calldata _description) external returns (string memory) {
            require(msg.sender == ownerOf(tokenId), "not your Gnar");
            customDescription[tokenId] = _description;
            string memory returnMessage = string(abi.encodePacked("Description set to: ", viewDescription(tokenId)));
            return returnMessage;
        }
        function viewDescription(uint256 tokenId) public view returns (string memory) {
            string memory description = "";
            string memory gnarId = tokenId.toString();
            if (bytes(customDescription[tokenId]).length != 0) {
                description = string(abi.encodePacked(description, "Gnar ", gnarId, " is a ", customDescription[tokenId]));
            } else {
                description = string(abi.encodePacked(description, "Gnar ", gnarId, " is a member of Gnars DAO"));
            }
            return description;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
    pragma solidity ^0.8.0;
    import "../utils/Context.sol";
    /**
     * @dev Contract module which provides a basic access control mechanism, where
     * there is an account (an owner) that can be granted exclusive access to
     * specific functions.
     *
     * By default, the owner account will be the one that deploys the contract. This
     * can later be changed with {transferOwnership}.
     *
     * This module is used through inheritance. It will make available the modifier
     * `onlyOwner`, which can be applied to your functions to restrict their use to
     * the owner.
     */
    abstract contract Ownable is Context {
        address private _owner;
        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
        /**
         * @dev Initializes the contract setting the deployer as the initial owner.
         */
        constructor() {
            _transferOwnership(_msgSender());
        }
        /**
         * @dev Returns the address of the current owner.
         */
        function owner() public view virtual returns (address) {
            return _owner;
        }
        /**
         * @dev Throws if called by any account other than the owner.
         */
        modifier onlyOwner() {
            require(owner() == _msgSender(), "Ownable: caller is not the owner");
            _;
        }
        /**
         * @dev Leaves the contract without owner. It will not be possible to call
         * `onlyOwner` functions anymore. Can only be called by the current owner.
         *
         * NOTE: Renouncing ownership will leave the contract without an owner,
         * thereby removing any functionality that is only available to the owner.
         */
        function renounceOwnership() public virtual onlyOwner {
            _transferOwnership(address(0));
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Can only be called by the current owner.
         */
        function transferOwnership(address newOwner) public virtual onlyOwner {
            require(newOwner != address(0), "Ownable: new owner is the zero address");
            _transferOwnership(newOwner);
        }
        /**
         * @dev Transfers ownership of the contract to a new account (`newOwner`).
         * Internal function without access restriction.
         */
        function _transferOwnership(address newOwner) internal virtual {
            address oldOwner = _owner;
            _owner = newOwner;
            emit OwnershipTransferred(oldOwner, newOwner);
        }
    }
    // SPDX-License-Identifier: MIT
    /// @title ERC721 Token Implementation
    /*********************************
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░██░░░████░░██░░░████░░░ *
     * ░░██████░░░████████░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     *********************************/
    // LICENSE
    // ERC721.sol modifies OpenZeppelin's ERC721.sol:
    // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6618f9f18424ade44116d0221719f4c93be6a078/contracts/token/ERC721/ERC721.sol
    //
    // ERC721.sol source code copyright OpenZeppelin licensed under the MIT License.
    // With modifications by Nounders DAO.
    //
    //
    // MODIFICATIONS:
    // `_safeMint` and `_mint` contain an additional `creator` argument and
    // emit two `Transfer` logs, rather than one. The first log displays the
    // transfer (mint) from `address(0)` to the `creator`. The second displays the
    // transfer from the `creator` to the `to` address. This enables correct
    // attribution on various NFT marketplaces.
    pragma solidity ^0.8.6;
    import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
    import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol';
    import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol';
    import '@openzeppelin/contracts/utils/Address.sol';
    import '@openzeppelin/contracts/utils/Context.sol';
    import '@openzeppelin/contracts/utils/Strings.sol';
    import '@openzeppelin/contracts/utils/introspection/ERC165.sol';
    /**
     * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
     * the Metadata extension, but not including the Enumerable extension, which is available separately as
     * {ERC721Enumerable}.
     */
    contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
        using Address for address;
        using Strings for uint256;
        // Token name
        string private _name;
        // Token symbol
        string private _symbol;
        // Mapping from token ID to owner address
        mapping(uint256 => address) private _owners;
        // Mapping owner address to token count
        mapping(address => uint256) private _balances;
        // Mapping from token ID to approved address
        mapping(uint256 => address) private _tokenApprovals;
        // Mapping from owner to operator approvals
        mapping(address => mapping(address => bool)) private _operatorApprovals;
        /**
         * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
         */
        constructor(string memory name_, string memory symbol_) {
            _name = name_;
            _symbol = symbol_;
        }
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
            return
                interfaceId == type(IERC721).interfaceId ||
                interfaceId == type(IERC721Metadata).interfaceId ||
                super.supportsInterface(interfaceId);
        }
        /**
         * @dev See {IERC721-balanceOf}.
         */
        function balanceOf(address owner) public view virtual override returns (uint256) {
            require(owner != address(0), 'ERC721: balance query for the zero address');
            return _balances[owner];
        }
        /**
         * @dev See {IERC721-ownerOf}.
         */
        function ownerOf(uint256 tokenId) public view virtual override returns (address) {
            address owner = _owners[tokenId];
            require(owner != address(0), 'ERC721: owner query for nonexistent token');
            return owner;
        }
        /**
         * @dev See {IERC721Metadata-name}.
         */
        function name() public view virtual override returns (string memory) {
            return _name;
        }
        /**
         * @dev See {IERC721Metadata-symbol}.
         */
        function symbol() public view virtual override returns (string memory) {
            return _symbol;
        }
        /**
         * @dev See {IERC721Metadata-tokenURI}.
         */
        function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
            require(_exists(tokenId), 'ERC721Metadata: URI query for nonexistent token');
            string memory baseURI = _baseURI();
            return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : '';
        }
        /**
         * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
         * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
         * by default, can be overriden in child contracts.
         */
        function _baseURI() internal view virtual returns (string memory) {
            return '';
        }
        /**
         * @dev See {IERC721-approve}.
         */
        function approve(address to, uint256 tokenId) public virtual override {
            address owner = ERC721.ownerOf(tokenId);
            require(to != owner, 'ERC721: approval to current owner');
            require(
                _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
                'ERC721: approve caller is not owner nor approved for all'
            );
            _approve(to, tokenId);
        }
        /**
         * @dev See {IERC721-getApproved}.
         */
        function getApproved(uint256 tokenId) public view virtual override returns (address) {
            require(_exists(tokenId), 'ERC721: approved query for nonexistent token');
            return _tokenApprovals[tokenId];
        }
        /**
         * @dev See {IERC721-setApprovalForAll}.
         */
        function setApprovalForAll(address operator, bool approved) public virtual override {
            require(operator != _msgSender(), 'ERC721: approve to caller');
            _operatorApprovals[_msgSender()][operator] = approved;
            emit ApprovalForAll(_msgSender(), operator, approved);
        }
        /**
         * @dev See {IERC721-isApprovedForAll}.
         */
        function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
            return _operatorApprovals[owner][operator];
        }
        /**
         * @dev See {IERC721-transferFrom}.
         */
        function transferFrom(
            address from,
            address to,
            uint256 tokenId
        ) public virtual override {
            //solhint-disable-next-line max-line-length
            require(_isApprovedOrOwner(_msgSender(), tokenId), 'ERC721: transfer caller is not owner nor approved');
            _transfer(from, to, tokenId);
        }
        /**
         * @dev See {IERC721-safeTransferFrom}.
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId
        ) public virtual override {
            safeTransferFrom(from, to, tokenId, '');
        }
        /**
         * @dev See {IERC721-safeTransferFrom}.
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId,
            bytes memory _data
        ) public virtual override {
            require(_isApprovedOrOwner(_msgSender(), tokenId), 'ERC721: transfer caller is not owner nor approved');
            _safeTransfer(from, to, tokenId, _data);
        }
        /**
         * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
         * are aware of the ERC721 protocol to prevent tokens from being forever locked.
         *
         * `_data` is additional data, it has no specified format and it is sent in call to `to`.
         *
         * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
         * implement alternative mechanisms to perform token transfer, such as signature-based.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function _safeTransfer(
            address from,
            address to,
            uint256 tokenId,
            bytes memory _data
        ) internal virtual {
            _transfer(from, to, tokenId);
            require(_checkOnERC721Received(from, to, tokenId, _data), 'ERC721: transfer to non ERC721Receiver implementer');
        }
        /**
         * @dev Returns whether `tokenId` exists.
         *
         * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
         *
         * Tokens start existing when they are minted (`_mint`),
         * and stop existing when they are burned (`_burn`).
         */
        function _exists(uint256 tokenId) internal view virtual returns (bool) {
            return _owners[tokenId] != address(0);
        }
        /**
         * @dev Returns whether `spender` is allowed to manage `tokenId`.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
            require(_exists(tokenId), 'ERC721: operator query for nonexistent token');
            address owner = ERC721.ownerOf(tokenId);
            return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
        }
        /**
         * @dev Safely mints `tokenId`, transfers it to `to`, and emits two log events -
         * 1. Credits the `minter` with the mint.
         * 2. Shows transfer from the `minter` to `to`.
         *
         * Requirements:
         *
         * - `tokenId` must not exist.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function _safeMint(
            address creator,
            address to,
            uint256 tokenId
        ) internal virtual {
            _safeMint(creator, to, tokenId, '');
        }
        /**
         * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
         * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
         */
        function _safeMint(
            address creator,
            address to,
            uint256 tokenId,
            bytes memory _data
        ) internal virtual {
            _mint(creator, to, tokenId);
            require(
                _checkOnERC721Received(address(0), to, tokenId, _data),
                'ERC721: transfer to non ERC721Receiver implementer'
            );
        }
        /**
         * @dev Mints `tokenId`, transfers it to `to`, and emits two log events -
         * 1. Credits the `creator` with the mint.
         * 2. Shows transfer from the `creator` to `to`.
         *
         * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
         *
         * Requirements:
         *
         * - `tokenId` must not exist.
         * - `to` cannot be the zero address.
         *
         * Emits a {Transfer} event.
         */
        function _mint(
            address creator,
            address to,
            uint256 tokenId
        ) internal virtual {
            require(to != address(0), 'ERC721: mint to the zero address');
            require(!_exists(tokenId), 'ERC721: token already minted');
            _beforeTokenTransfer(address(0), to, tokenId);
            _balances[to] += 1;
            _owners[tokenId] = to;
            emit Transfer(address(0), creator, tokenId);
            emit Transfer(creator, to, tokenId);
        }
        /**
         * @dev Destroys `tokenId`.
         * The approval is cleared when the token is burned.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         *
         * Emits a {Transfer} event.
         */
        function _burn(uint256 tokenId) internal virtual {
            address owner = ERC721.ownerOf(tokenId);
            _beforeTokenTransfer(owner, address(0), tokenId);
            // Clear approvals
            _approve(address(0), tokenId);
            _balances[owner] -= 1;
            delete _owners[tokenId];
            emit Transfer(owner, address(0), tokenId);
        }
        /**
         * @dev Transfers `tokenId` from `from` to `to`.
         *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
         *
         * Requirements:
         *
         * - `to` cannot be the zero address.
         * - `tokenId` token must be owned by `from`.
         *
         * Emits a {Transfer} event.
         */
        function _transfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual {
            require(ERC721.ownerOf(tokenId) == from, 'ERC721: transfer of token that is not own');
            require(to != address(0), 'ERC721: transfer to the zero address');
            _beforeTokenTransfer(from, to, tokenId);
            // Clear approvals from the previous owner
            _approve(address(0), tokenId);
            _balances[from] -= 1;
            _balances[to] += 1;
            _owners[tokenId] = to;
            emit Transfer(from, to, tokenId);
        }
        /**
         * @dev Approve `to` to operate on `tokenId`
         *
         * Emits a {Approval} event.
         */
        function _approve(address to, uint256 tokenId) internal virtual {
            _tokenApprovals[tokenId] = to;
            emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
        }
        /**
         * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
         * The call is not executed if the target address is not a contract.
         *
         * @param from address representing the previous owner of the given token ID
         * @param to target address that will receive the tokens
         * @param tokenId uint256 ID of the token to be transferred
         * @param _data bytes optional data to send along with the call
         * @return bool whether the call correctly returned the expected magic value
         */
        function _checkOnERC721Received(
            address from,
            address to,
            uint256 tokenId,
            bytes memory _data
        ) private returns (bool) {
            if (to.isContract()) {
                try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) {
                    return retval == IERC721Receiver(to).onERC721Received.selector;
                } catch (bytes memory reason) {
                    if (reason.length == 0) {
                        revert('ERC721: transfer to non ERC721Receiver implementer');
                    } else {
                        assembly {
                            revert(add(32, reason), mload(reason))
                        }
                    }
                }
            } else {
                return true;
            }
        }
        /**
         * @dev Hook that is called before any token transfer. This includes minting
         * and burning.
         *
         * Calling conditions:
         *
         * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
         * transferred to `to`.
         * - When `from` is zero, `tokenId` will be minted for `to`.
         * - When `to` is zero, ``from``'s `tokenId` will be burned.
         * - `from` and `to` are never both zero.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual {}
    }
    // SPDX-License-Identifier: BSD-3-Clause
    /// @title Vote checkpointing for an ERC-721 token
    /*********************************
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░██░░░████░░██░░░████░░░ *
     * ░░██████░░░████████░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     *********************************/
    // LICENSE
    // ERC721Checkpointable.sol uses and modifies part of Compound Lab's Comp.sol:
    // https://github.com/compound-finance/compound-protocol/blob/ae4388e780a8d596d97619d9704a931a2752c2bc/contracts/Governance/Comp.sol
    //
    // Comp.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.
    // With modifications by Nounders DAO.
    //
    // Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause
    //
    // MODIFICATIONS
    // Checkpointing logic from Comp.sol has been used with the following modifications:
    // - `delegates` is renamed to `_delegates` and is set to private
    // - `delegates` is a public function that uses the `_delegates` mapping look-up, but unlike
    //   Comp.sol, returns the delegator's own address if there is no delegate.
    //   This avoids the delegator needing to "delegate to self" with an additional transaction
    // - `_transferTokens()` is renamed `_beforeTokenTransfer()` and adapted to hook into OpenZeppelin's ERC721 hooks.
    pragma solidity ^0.8.6;
    import './ERC721Enumerable.sol';
    abstract contract ERC721Checkpointable is ERC721Enumerable {
        /// @notice Defines decimals as per ERC-20 convention to make integrations with 3rd party governance platforms easier
        uint8 public constant decimals = 0;
        /// @notice A record of each accounts delegate
        mapping(address => address) private _delegates;
        /// @notice A checkpoint for marking number of votes from a given block
        struct Checkpoint {
            uint32 fromBlock;
            uint96 votes;
        }
        /// @notice A record of votes checkpoints for each account, by index
        mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;
        /// @notice The number of checkpoints for each account
        mapping(address => uint32) public numCheckpoints;
        /// @notice The EIP-712 typehash for the contract's domain
        bytes32 public constant DOMAIN_TYPEHASH =
            keccak256('EIP712Domain(string name,uint256 chainId,address verifyingContract)');
        /// @notice The EIP-712 typehash for the delegation struct used by the contract
        bytes32 public constant DELEGATION_TYPEHASH =
            keccak256('Delegation(address delegatee,uint256 nonce,uint256 expiry)');
        /// @notice A record of states for signing / validating signatures
        mapping(address => uint256) public nonces;
        /// @notice An event thats emitted when an account changes its delegate
        event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);
        /// @notice An event thats emitted when a delegate account's vote balance changes
        event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);
        /**
         * @notice The votes a delegator can delegate, which is the current balance of the delegator.
         * @dev Used when calling `_delegate()`
         */
        function votesToDelegate(address delegator) public view returns (uint96) {
            return safe96(balanceOf(delegator), 'ERC721Checkpointable::votesToDelegate: amount exceeds 96 bits');
        }
        /**
         * @notice Overrides the standard `Comp.sol` delegates mapping to return
         * the delegator's own address if they haven't delegated.
         * This avoids having to delegate to oneself.
         */
        function delegates(address delegator) public view returns (address) {
            address current = _delegates[delegator];
            return current == address(0) ? delegator : current;
        }
        /**
         * @notice Adapted from `_transferTokens()` in `Comp.sol` to update delegate votes.
         * @dev hooks into OpenZeppelin's `ERC721._transfer`
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal override {
            super._beforeTokenTransfer(from, to, tokenId);
            /// @notice Differs from `_transferTokens()` to use `delegates` override method to simulate auto-delegation
            _moveDelegates(delegates(from), delegates(to), 1);
        }
        /**
         * @notice Delegate votes from `msg.sender` to `delegatee`
         * @param delegatee The address to delegate votes to
         */
        function delegate(address delegatee) public {
            if (delegatee == address(0)) delegatee = msg.sender;
            return _delegate(msg.sender, delegatee);
        }
        /**
         * @notice Delegates votes from signatory to `delegatee`
         * @param delegatee The address to delegate votes to
         * @param nonce The contract state required to match the signature
         * @param expiry The time at which to expire the signature
         * @param v The recovery byte of the signature
         * @param r Half of the ECDSA signature pair
         * @param s Half of the ECDSA signature pair
         */
        function delegateBySig(
            address delegatee,
            uint256 nonce,
            uint256 expiry,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) public {
            bytes32 domainSeparator = keccak256(
                abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), getChainId(), address(this))
            );
            bytes32 structHash = keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry));
            bytes32 digest = keccak256(abi.encodePacked('\\x19\\x01', domainSeparator, structHash));
            address signatory = ecrecover(digest, v, r, s);
            require(signatory != address(0), 'ERC721Checkpointable::delegateBySig: invalid signature');
            require(nonce == nonces[signatory]++, 'ERC721Checkpointable::delegateBySig: invalid nonce');
            require(block.timestamp <= expiry, 'ERC721Checkpointable::delegateBySig: signature expired');
            return _delegate(signatory, delegatee);
        }
        /**
         * @notice Gets the current votes balance for `account`
         * @param account The address to get votes balance
         * @return The number of current votes for `account`
         */
        function getCurrentVotes(address account) external view returns (uint96) {
            uint32 nCheckpoints = numCheckpoints[account];
            return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;
        }
        /**
         * @notice Determine the prior number of votes for an account as of a block number
         * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.
         * @param account The address of the account to check
         * @param blockNumber The block number to get the vote balance at
         * @return The number of votes the account had as of the given block
         */
        function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {
            require(blockNumber < block.number, 'ERC721Checkpointable::getPriorVotes: not yet determined');
            uint32 nCheckpoints = numCheckpoints[account];
            if (nCheckpoints == 0) {
                return 0;
            }
            // First check most recent balance
            if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {
                return checkpoints[account][nCheckpoints - 1].votes;
            }
            // Next check implicit zero balance
            if (checkpoints[account][0].fromBlock > blockNumber) {
                return 0;
            }
            uint32 lower = 0;
            uint32 upper = nCheckpoints - 1;
            while (upper > lower) {
                uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
                Checkpoint memory cp = checkpoints[account][center];
                if (cp.fromBlock == blockNumber) {
                    return cp.votes;
                } else if (cp.fromBlock < blockNumber) {
                    lower = center;
                } else {
                    upper = center - 1;
                }
            }
            return checkpoints[account][lower].votes;
        }
        function _delegate(address delegator, address delegatee) internal {
            /// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation
            address currentDelegate = delegates(delegator);
            _delegates[delegator] = delegatee;
            emit DelegateChanged(delegator, currentDelegate, delegatee);
            uint96 amount = votesToDelegate(delegator);
            _moveDelegates(currentDelegate, delegatee, amount);
        }
        function _moveDelegates(
            address srcRep,
            address dstRep,
            uint96 amount
        ) internal {
            if (srcRep != dstRep && amount > 0) {
                if (srcRep != address(0)) {
                    uint32 srcRepNum = numCheckpoints[srcRep];
                    uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
                    uint96 srcRepNew = sub96(srcRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount underflows');
                    _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
                }
                if (dstRep != address(0)) {
                    uint32 dstRepNum = numCheckpoints[dstRep];
                    uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
                    uint96 dstRepNew = add96(dstRepOld, amount, 'ERC721Checkpointable::_moveDelegates: amount overflows');
                    _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
                }
            }
        }
        function _writeCheckpoint(
            address delegatee,
            uint32 nCheckpoints,
            uint96 oldVotes,
            uint96 newVotes
        ) internal {
            uint32 blockNumber = safe32(
                block.number,
                'ERC721Checkpointable::_writeCheckpoint: block number exceeds 32 bits'
            );
            if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {
                checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;
            } else {
                checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);
                numCheckpoints[delegatee] = nCheckpoints + 1;
            }
            emit DelegateVotesChanged(delegatee, oldVotes, newVotes);
        }
        function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {
            require(n < 2**32, errorMessage);
            return uint32(n);
        }
        function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) {
            require(n < 2**96, errorMessage);
            return uint96(n);
        }
        function add96(
            uint96 a,
            uint96 b,
            string memory errorMessage
        ) internal pure returns (uint96) {
            uint96 c = a + b;
            require(c >= a, errorMessage);
            return c;
        }
        function sub96(
            uint96 a,
            uint96 b,
            string memory errorMessage
        ) internal pure returns (uint96) {
            require(b <= a, errorMessage);
            return a - b;
        }
        function getChainId() internal view returns (uint256) {
            uint256 chainId;
            assembly {
                chainId := chainid()
            }
            return chainId;
        }
    }
    // SPDX-License-Identifier: GPL-3.0
    /// @title Interface for Gnar
    /*********************************
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░██░░░████░░██░░░████░░░ *
     * ░░██████░░░████████░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     *********************************/
    pragma solidity 0.8.6;
    import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
    import {IGnarDescriptorV2} from "./IGNARDescriptorV2.sol";
    import {IGnarSeederV2} from "./IGNARSeederV2.sol";
    interface ISkateContractV2 is IERC721 {
        event GnarCreated(uint256 indexed tokenId, IGnarSeederV2.Seed seed);
        event GnarBurned(uint256 indexed tokenId);
        event MinterUpdated(address minter);
        event MinterLocked();
        event DescriptorUpdated(IGnarDescriptorV2 descriptor);
        event DescriptorLocked();
        event SeederUpdated(IGnarSeederV2 seeder);
        event SeederLocked();
        function mint() external returns (uint256);
        function burn(uint256 tokenId) external;
        function setMinter(address minter) external;
        function lockMinter() external;
        function setDescriptor(IGnarDescriptorV2 descriptor) external;
        function lockDescriptor() external;
        function setSeeder(IGnarSeederV2 seeder) external;
        function lockSeeder() external;
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)
    pragma solidity ^0.8.0;
    import "../../utils/introspection/IERC165.sol";
    /**
     * @dev Required interface of an ERC721 compliant contract.
     */
    interface IERC721 is IERC165 {
        /**
         * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
         */
        event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
        /**
         * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
         */
        event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
        /**
         * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
         */
        event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
        /**
         * @dev Returns the number of tokens in ``owner``'s account.
         */
        function balanceOf(address owner) external view returns (uint256 balance);
        /**
         * @dev Returns the owner of the `tokenId` token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function ownerOf(uint256 tokenId) external view returns (address owner);
        /**
         * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
         * are aware of the ERC721 protocol to prevent tokens from being forever locked.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external;
        /**
         * @dev Transfers `tokenId` token from `from` to `to`.
         *
         * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must be owned by `from`.
         * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(
            address from,
            address to,
            uint256 tokenId
        ) external;
        /**
         * @dev Gives permission to `to` to transfer `tokenId` token to another account.
         * The approval is cleared when the token is transferred.
         *
         * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
         *
         * Requirements:
         *
         * - The caller must own the token or be an approved operator.
         * - `tokenId` must exist.
         *
         * Emits an {Approval} event.
         */
        function approve(address to, uint256 tokenId) external;
        /**
         * @dev Returns the account approved for `tokenId` token.
         *
         * Requirements:
         *
         * - `tokenId` must exist.
         */
        function getApproved(uint256 tokenId) external view returns (address operator);
        /**
         * @dev Approve or remove `operator` as an operator for the caller.
         * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
         *
         * Requirements:
         *
         * - The `operator` cannot be the caller.
         *
         * Emits an {ApprovalForAll} event.
         */
        function setApprovalForAll(address operator, bool _approved) external;
        /**
         * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
         *
         * See {setApprovalForAll}
         */
        function isApprovedForAll(address owner, address operator) external view returns (bool);
        /**
         * @dev Safely transfers `tokenId` token from `from` to `to`.
         *
         * Requirements:
         *
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         * - `tokenId` token must exist and be owned by `from`.
         * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
         * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
         *
         * Emits a {Transfer} event.
         */
        function safeTransferFrom(
            address from,
            address to,
            uint256 tokenId,
            bytes calldata data
        ) external;
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity ^0.8.6;
    import {IGnarDescriptorV2} from "./IGNARDescriptorV2.sol";
    interface IGnarSeederV2 {
        struct Seed {
            uint48 background;
            uint48 body;
            uint48 accessory;
            uint48 head;
            uint48 glasses;
        }
        function generateSeed(uint256 gnarId, IGnarDescriptorV2 descriptor) external view returns (Seed memory);
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity ^0.8.6;
    import {IGnarSeederV2} from "./IGNARSeederV2.sol";
    import {IGnarDecorator} from "../interfaces/IGnarDecorator.sol";
    interface IGnarDescriptorV2 {
        event PartsLocked();
        event DataURIToggled(bool enabled);
        event BaseURIUpdated(string baseURI);
        event DecoratorUpdated(IGnarDecorator decorator);
        function setDecorator(IGnarDecorator _decorator) external;
        function arePartsLocked() external returns (bool);
        function isDataURIEnabled() external returns (bool);
        function baseURI() external returns (string memory);
        function palettes(uint8 paletteIndex, uint256 colorIndex) external view returns (string memory);
        function backgrounds(uint256 index) external view returns (string memory);
        function bodies(uint256 index) external view returns (bytes memory);
        function accessories(uint256 index) external view returns (bytes memory);
        function heads(uint256 index) external view returns (bytes memory);
        function glasses(uint256 index) external view returns (bytes memory);
        function backgroundCount() external view returns (uint256);
        function bodyCount() external view returns (uint256);
        function accessoryCount() external view returns (uint256);
        function headCount() external view returns (uint256);
        function glassesCount() external view returns (uint256);
        function addManyColorsToPalette(uint8 paletteIndex, string[] calldata newColors) external;
        function addManyBackgrounds(string[] calldata backgrounds) external;
        function addManyBodies(bytes[] calldata bodies) external;
        function addManyAccessories(bytes[] calldata accessories) external;
        function addManyHeads(bytes[] calldata heads) external;
        function addManyGlasses(bytes[] calldata glasses) external;
        function addColorToPalette(uint8 paletteIndex, string calldata color) external;
        function addBackground(string calldata background) external;
        function addBody(bytes calldata body) external;
        function addAccessory(bytes calldata accessory) external;
        function addHead(bytes calldata head) external;
        function addGlasses(bytes calldata glasses) external;
        function lockParts() external;
        function toggleDataURIEnabled() external;
        function setBaseURI(string calldata baseURI) external;
        function tokenURI(uint256 tokenId, IGnarSeederV2.Seed memory seed) external view returns (string memory);
        function dataURI(uint256 tokenId, IGnarSeederV2.Seed memory seed) external view returns (string memory);
        function genericDataURI(
            string calldata name,
            string calldata description,
            IGnarSeederV2.Seed memory seed
        ) external view returns (string memory);
        function generateSVGImage(IGnarSeederV2.Seed memory seed) external view returns (string memory);
    }
    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.6;
    interface IProxyRegistry {
        function proxies(address) external view returns (address);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev String operations.
     */
    library Strings {
        bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
        /**
         * @dev Converts a `uint256` to its ASCII `string` decimal representation.
         */
        function toString(uint256 value) internal pure returns (string memory) {
            // Inspired by OraclizeAPI's implementation - MIT licence
            // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
            if (value == 0) {
                return "0";
            }
            uint256 temp = value;
            uint256 digits;
            while (temp != 0) {
                digits++;
                temp /= 10;
            }
            bytes memory buffer = new bytes(digits);
            while (value != 0) {
                digits -= 1;
                buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
                value /= 10;
            }
            return string(buffer);
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
         */
        function toHexString(uint256 value) internal pure returns (string memory) {
            if (value == 0) {
                return "0x00";
            }
            uint256 temp = value;
            uint256 length = 0;
            while (temp != 0) {
                length++;
                temp >>= 8;
            }
            return toHexString(value, length);
        }
        /**
         * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
         */
        function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
            bytes memory buffer = new bytes(2 * length + 2);
            buffer[0] = "0";
            buffer[1] = "x";
            for (uint256 i = 2 * length + 1; i > 1; --i) {
                buffer[i] = _HEX_SYMBOLS[value & 0xf];
                value >>= 4;
            }
            require(value == 0, "Strings: hex length insufficient");
            return string(buffer);
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Provides information about the current execution context, including the
     * sender of the transaction and its data. While these are generally available
     * via msg.sender and msg.data, they should not be accessed in such a direct
     * manner, since when dealing with meta-transactions the account sending and
     * paying for execution may not be the actual sender (as far as an application
     * is concerned).
     *
     * This contract is only required for intermediate, library-like contracts.
     */
    abstract contract Context {
        function _msgSender() internal view virtual returns (address) {
            return msg.sender;
        }
        function _msgData() internal view virtual returns (bytes calldata) {
            return msg.data;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721Receiver.sol)
    pragma solidity ^0.8.0;
    /**
     * @title ERC721 token receiver interface
     * @dev Interface for any contract that wants to support safeTransfers
     * from ERC721 asset contracts.
     */
    interface IERC721Receiver {
        /**
         * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
         * by `operator` from `from`, this function is called.
         *
         * It must return its Solidity selector to confirm the token transfer.
         * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
         *
         * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.
         */
        function onERC721Received(
            address operator,
            address from,
            uint256 tokenId,
            bytes calldata data
        ) external returns (bytes4);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)
    pragma solidity ^0.8.0;
    import "../IERC721.sol";
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721Metadata is IERC721 {
        /**
         * @dev Returns the token collection name.
         */
        function name() external view returns (string memory);
        /**
         * @dev Returns the token collection symbol.
         */
        function symbol() external view returns (string memory);
        /**
         * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
         */
        function tokenURI(uint256 tokenId) external view returns (string memory);
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Collection of functions related to the address type
     */
    library Address {
        /**
         * @dev Returns true if `account` is a contract.
         *
         * [IMPORTANT]
         * ====
         * It is unsafe to assume that an address for which this function returns
         * false is an externally-owned account (EOA) and not a contract.
         *
         * Among others, `isContract` will return false for the following
         * types of addresses:
         *
         *  - an externally-owned account
         *  - a contract in construction
         *  - an address where a contract will be created
         *  - an address where a contract lived, but was destroyed
         * ====
         */
        function isContract(address account) internal view returns (bool) {
            // This method relies on extcodesize, which returns 0 for contracts in
            // construction, since the code is only stored at the end of the
            // constructor execution.
            uint256 size;
            assembly {
                size := extcodesize(account)
            }
            return size > 0;
        }
        /**
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
         * `recipient`, forwarding all available gas and reverting on errors.
         *
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
         * imposed by `transfer`, making them unable to receive funds via
         * `transfer`. {sendValue} removes this limitation.
         *
         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
         *
         * IMPORTANT: because control is transferred to `recipient`, care must be
         * taken to not create reentrancy vulnerabilities. Consider using
         * {ReentrancyGuard} or the
         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
         */
        function sendValue(address payable recipient, uint256 amount) internal {
            require(address(this).balance >= amount, "Address: insufficient balance");
            (bool success, ) = recipient.call{value: amount}("");
            require(success, "Address: unable to send value, recipient may have reverted");
        }
        /**
         * @dev Performs a Solidity function call using a low level `call`. A
         * plain `call` is an unsafe replacement for a function call: use this
         * function instead.
         *
         * If `target` reverts with a revert reason, it is bubbled up by this
         * function (like regular Solidity function calls).
         *
         * Returns the raw returned data. To convert to the expected return value,
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
         *
         * Requirements:
         *
         * - `target` must be a contract.
         * - calling `target` with `data` must not revert.
         *
         * _Available since v3.1._
         */
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionCall(target, data, "Address: low-level call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
         * `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, 0, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but also transferring `value` wei to `target`.
         *
         * Requirements:
         *
         * - the calling contract must have an ETH balance of at least `value`.
         * - the called Solidity function must be `payable`.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value
        ) internal returns (bytes memory) {
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
        }
        /**
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
         * with `errorMessage` as a fallback revert reason when `target` reverts.
         *
         * _Available since v3.1._
         */
        function functionCallWithValue(
            address target,
            bytes memory data,
            uint256 value,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(address(this).balance >= value, "Address: insufficient balance for call");
            require(isContract(target), "Address: call to non-contract");
            (bool success, bytes memory returndata) = target.call{value: value}(data);
            return verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
            return functionStaticCall(target, data, "Address: low-level static call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a static call.
         *
         * _Available since v3.3._
         */
        function functionStaticCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal view returns (bytes memory) {
            require(isContract(target), "Address: static call to non-contract");
            (bool success, bytes memory returndata) = target.staticcall(data);
            return verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
        }
        /**
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
         * but performing a delegate call.
         *
         * _Available since v3.4._
         */
        function functionDelegateCall(
            address target,
            bytes memory data,
            string memory errorMessage
        ) internal returns (bytes memory) {
            require(isContract(target), "Address: delegate call to non-contract");
            (bool success, bytes memory returndata) = target.delegatecall(data);
            return verifyCallResult(success, returndata, errorMessage);
        }
        /**
         * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
         * revert reason using the provided one.
         *
         * _Available since v4.3._
         */
        function verifyCallResult(
            bool success,
            bytes memory returndata,
            string memory errorMessage
        ) internal pure returns (bytes memory) {
            if (success) {
                return returndata;
            } else {
                // Look for revert reason and bubble it up if present
                if (returndata.length > 0) {
                    // The easiest way to bubble the revert reason is using memory via assembly
                    assembly {
                        let returndata_size := mload(returndata)
                        revert(add(32, returndata), returndata_size)
                    }
                } else {
                    revert(errorMessage);
                }
            }
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)
    pragma solidity ^0.8.0;
    import "./IERC165.sol";
    /**
     * @dev Implementation of the {IERC165} interface.
     *
     * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
     * for the additional interface id that will be supported. For example:
     *
     * ```solidity
     * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
     *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
     * }
     * ```
     *
     * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
     */
    abstract contract ERC165 is IERC165 {
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
            return interfaceId == type(IERC165).interfaceId;
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
    pragma solidity ^0.8.0;
    /**
     * @dev Interface of the ERC165 standard, as defined in the
     * https://eips.ethereum.org/EIPS/eip-165[EIP].
     *
     * Implementers can declare support of contract interfaces, which can then be
     * queried by others ({ERC165Checker}).
     *
     * For an implementation, see {ERC165}.
     */
    interface IERC165 {
        /**
         * @dev Returns true if this contract implements the interface defined by
         * `interfaceId`. See the corresponding
         * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
         * to learn more about how these ids are created.
         *
         * This function call must use less than 30 000 gas.
         */
        function supportsInterface(bytes4 interfaceId) external view returns (bool);
    }
    // SPDX-License-Identifier: MIT
    /// @title ERC721 Enumerable Extension
    /*********************************
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░██░░░████░░██░░░████░░░ *
     * ░░██████░░░████████░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░██░░██░░░████░░██░░░████░░░ *
     * ░░░░░░█████████░░█████████░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *
     *********************************/
    // LICENSE
    // ERC721.sol modifies OpenZeppelin's ERC721Enumerable.sol:
    // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/6618f9f18424ade44116d0221719f4c93be6a078/contracts/token/ERC721/extensions/ERC721Enumerable.sol
    //
    // ERC721Enumerable.sol source code copyright OpenZeppelin licensed under the MIT License.
    // With modifications by Nounders DAO.
    //
    // MODIFICATIONS:
    // Consumes modified `ERC721` contract. See notes in `ERC721.sol`.
    pragma solidity ^0.8.0;
    import './ERC721.sol';
    import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol';
    /**
     * @dev This implements an optional extension of {ERC721} defined in the EIP that adds
     * enumerability of all the token ids in the contract as well as all token ids owned by each
     * account.
     */
    abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
        // Mapping from owner to list of owned token IDs
        mapping(address => mapping(uint256 => uint256)) private _ownedTokens;
        // Mapping from token ID to index of the owner tokens list
        mapping(uint256 => uint256) private _ownedTokensIndex;
        // Array with all token ids, used for enumeration
        uint256[] private _allTokens;
        // Mapping from token id to position in the allTokens array
        mapping(uint256 => uint256) private _allTokensIndex;
        /**
         * @dev See {IERC165-supportsInterface}.
         */
        function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
            return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
        }
        /**
         * @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
         */
        function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual override returns (uint256) {
            require(index < ERC721.balanceOf(owner), 'ERC721Enumerable: owner index out of bounds');
            return _ownedTokens[owner][index];
        }
        /**
         * @dev See {IERC721Enumerable-totalSupply}.
         */
        function totalSupply() public view virtual override returns (uint256) {
            return _allTokens.length;
        }
        /**
         * @dev See {IERC721Enumerable-tokenByIndex}.
         */
        function tokenByIndex(uint256 index) public view virtual override returns (uint256) {
            require(index < ERC721Enumerable.totalSupply(), 'ERC721Enumerable: global index out of bounds');
            return _allTokens[index];
        }
        /**
         * @dev Hook that is called before any token transfer. This includes minting
         * and burning.
         *
         * Calling conditions:
         *
         * - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be
         * transferred to `to`.
         * - When `from` is zero, `tokenId` will be minted for `to`.
         * - When `to` is zero, ``from``'s `tokenId` will be burned.
         * - `from` cannot be the zero address.
         * - `to` cannot be the zero address.
         *
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
         */
        function _beforeTokenTransfer(
            address from,
            address to,
            uint256 tokenId
        ) internal virtual override {
            super._beforeTokenTransfer(from, to, tokenId);
            if (from == address(0)) {
                _addTokenToAllTokensEnumeration(tokenId);
            } else if (from != to) {
                _removeTokenFromOwnerEnumeration(from, tokenId);
            }
            if (to == address(0)) {
                _removeTokenFromAllTokensEnumeration(tokenId);
            } else if (to != from) {
                _addTokenToOwnerEnumeration(to, tokenId);
            }
        }
        /**
         * @dev Private function to add a token to this extension's ownership-tracking data structures.
         * @param to address representing the new owner of the given token ID
         * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
         */
        function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
            uint256 length = ERC721.balanceOf(to);
            _ownedTokens[to][length] = tokenId;
            _ownedTokensIndex[tokenId] = length;
        }
        /**
         * @dev Private function to add a token to this extension's token tracking data structures.
         * @param tokenId uint256 ID of the token to be added to the tokens list
         */
        function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
            _allTokensIndex[tokenId] = _allTokens.length;
            _allTokens.push(tokenId);
        }
        /**
         * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
         * while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
         * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
         * This has O(1) time complexity, but alters the order of the _ownedTokens array.
         * @param from address representing the previous owner of the given token ID
         * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
         */
        function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
            // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
            // then delete the last slot (swap and pop).
            uint256 lastTokenIndex = ERC721.balanceOf(from) - 1;
            uint256 tokenIndex = _ownedTokensIndex[tokenId];
            // When the token to delete is the last token, the swap operation is unnecessary
            if (tokenIndex != lastTokenIndex) {
                uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
                _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
            }
            // This also deletes the contents at the last position of the array
            delete _ownedTokensIndex[tokenId];
            delete _ownedTokens[from][lastTokenIndex];
        }
        /**
         * @dev Private function to remove a token from this extension's token tracking data structures.
         * This has O(1) time complexity, but alters the order of the _allTokens array.
         * @param tokenId uint256 ID of the token to be removed from the tokens list
         */
        function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
            // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
            // then delete the last slot (swap and pop).
            uint256 lastTokenIndex = _allTokens.length - 1;
            uint256 tokenIndex = _allTokensIndex[tokenId];
            // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
            // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
            // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
            uint256 lastTokenId = _allTokens[lastTokenIndex];
            _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
            _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
            // This also deletes the contents at the last position of the array
            delete _allTokensIndex[tokenId];
            _allTokens.pop();
        }
    }
    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Enumerable.sol)
    pragma solidity ^0.8.0;
    import "../IERC721.sol";
    /**
     * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
     * @dev See https://eips.ethereum.org/EIPS/eip-721
     */
    interface IERC721Enumerable is IERC721 {
        /**
         * @dev Returns the total amount of tokens stored by the contract.
         */
        function totalSupply() external view returns (uint256);
        /**
         * @dev Returns a token ID owned by `owner` at a given `index` of its token list.
         * Use along with {balanceOf} to enumerate all of ``owner``'s tokens.
         */
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
        /**
         * @dev Returns a token ID at a given `index` of all the tokens stored by the contract.
         * Use along with {totalSupply} to enumerate all tokens.
         */
        function tokenByIndex(uint256 index) external view returns (uint256);
    }
    // SPDX-License-Identifier: GPL-3.0
    pragma solidity ^0.8.6;
    import {IGnarSeederV2} from "./IGNARSeederV2.sol";
    interface IGnarDecorator {
        function backgrounds(uint256 index) external view returns (string memory);
        function bodies(uint256 index) external view returns (string memory);
        function accessories(uint256 index) external view returns (string memory);
        function heads(uint256 index) external view returns (string memory);
        function glasses(uint256 index) external view returns (string memory);
        function addManyBackgrounds(string[] calldata _backgrounds) external;
        function addManyBodies(string[] calldata _bodies) external;
        function addManyAccessories(string[] calldata _accessories) external;
        function addManyHeads(string[] calldata _heads) external;
        function addManyGlasses(string[] calldata _glasses) external;
    }