ETH Price: $3,313.35 (-0.36%)
Gas: 11 Gwei

Contract Diff Checker

Contract Name:
MoonCatLootprints

Contract Source Code:

// SPDX-License-Identifier: AGPL-3.0

pragma solidity 0.8.1;

/**
 * @dev On-chain art generation for MoonCatLootprints
 * Takes individual trait values as parameters, and outputs complete representations of them.
 */
contract MoonCatLootprintsMetadata {

    string[5] internal class_names =
        [
         "Mech",
         "Sub",
         "Tank",
         "Cruiser",
         "Unknown"
         ];

    /**
     * @dev Convert a Classification ID number into a string name
     */
    function getClassName(uint8 classId) public view returns (string memory) {
        return class_names[classId];
    }

    string[15] internal color_names =
        ["Hero Silver",
         "Genesis White",
         "Genesis Black",
         "Red",
         "Orange",
         "Yellow",
         "Chartreuse",
         "Green",
         "Teal",
         "Cyan",
         "SkyBlue",
         "Blue",
         "Purple",
         "Magenta",
         "Fuchsia"];

    /**
     * @dev Convert a Color ID number into a string name
     */
    function getColorName(uint8 colorId) public view returns (string memory) {
        return color_names[colorId];
    }

    // Color codes used for the background color of an image representation
    string[15] internal color_codes =
        ["#777777", // Silver
         "#cccccc", // White
         "#111111", // Black
         "hsl(0,60%,38%)", // Red
         "hsl(30,60%,38%)", // Orange
         "hsl(60,60%,38%)", // Yellow
         "hsl(80,60%,38%)", // Chartreuse
         "hsl(120,60%,38%)", // Green
         "hsl(150,60%,38%)", // Teal
         "hsl(180,60%,38%)", // Cyan
         "hsl(210,60%,38%)", // SkyBlue
         "hsl(240,60%,38%)", // Blue
         "hsl(270,60%,38%)", // Purple
         "hsl(300,60%,38%)", // Magenta
         "hsl(330,60%,38%)"]; // Fuchsia

    // SVG codes for the different icons for each ship classification
    string[4] public ship_images =
        ["<path class=\"s\" d=\"M-61.74,77.79h-12.61V32.32h12.61V77.79z M-28.03,26.64l-7.58-12.63v44.12h7.58V26.64z M-0.65,52.52h10.99 L41.41,1.36L24.74-12.66H-0.65h-25.39L-42.72,1.36l31.07,51.16H-0.65z M60.43,77.79h12.61V32.32H60.43V77.79z M26.73,58.14h7.58 V14.02l-7.58,12.63V58.14z\"/><path class=\"s\" d=\"M-23.89,32.56v4.77h-44.15V8.75h29.81 M-58.76,13.76h-18.55v18.55h18.55V13.76z M22.59,32.56v4.77h44.15V8.75 H36.92 M57.46,32.32h18.55V13.76H57.46V32.32z M5.79,46.98L5.79,46.98c0-1.07-0.87-1.94-1.94-1.94h-9c-1.07,0-1.94,0.87-1.94,1.94 v0c0,1.07,0.87,1.94,1.94,1.94h9C4.92,48.93,5.79,48.06,5.79,46.98z\"/><path class=\"s s1\" d=\"M-79.92,94.43V86.1 M-56.04,94.43V86.1 M78.61,94.43V86.1 M54.74,94.43V86.1 M-14.48,5.33h28.04 M-9.45,1.1 H8.52\"/><path class=\"s s1\" d=\"M-44.11,94.43h-47.87V82.76c0-2.76,2.24-5,5-5h37.87c2.76,0,5,2.24,5,5V94.43z M-19.88,57.67v-6.18 c0-1.64-1.33-2.97-2.97-2.97h-9.15v12.13h9.15C-21.22,60.65-19.88,59.32-19.88,57.67z M42.8,94.43h47.87V82.76c0-2.76-2.24-5-5-5 H47.8c-2.76,0-5,2.24-5,5V94.43z M-0.65,31.11h14.08L33.42,3.86L25.39,2.2l-8.96,8.83H-0.65h-17.08l-8.96-8.83l-8.04,1.66 l19.99,27.25H-0.65z M21.55,60.65h9.15V48.52h-9.15c-1.64,0-2.97,1.33-2.97,2.97v6.18C18.58,59.32,19.91,60.65,21.55,60.65z\"/><path class=\"s s1\" d=\"M-26.04-12.66l-11.17,9.4v-27.46h7.51l16.17,18.06H-26.04z M24.74-12.66l11.17,9.4v-27.46H28.4L12.23-12.66 H24.74z\"/><path class=\"s s2\" d=\"M-19.88,52.86h-3.79 M-19.88,56.46h-3.79 M22.37,52.86h-3.79 M18.58,56.46h3.79\"/>  <path class=\"s s2\" d=\"M-39.67,8.41l-1.58,33.83h-11.47l-1.58-33.83c0-4.04,3.28-7.32,7.32-7.32C-42.95,1.1-39.67,4.37-39.67,8.41z M-43.38,42.24h-6.9l-1.01,4.74h8.91L-43.38,42.24z M38.37,8.41l1.58,33.83h11.47L53,8.41c0-4.04-3.28-7.32-7.32-7.32 C41.64,1.1,38.37,4.37,38.37,8.41z M41.06,46.98h8.91l-1.01-4.74h-6.9L41.06,46.98z\"/>", // Mech

         "<path class=\"s\" d=\"M55.52,60.62l-125.85,7.15c-13.35,0.76-24.59-9.86-24.59-23.23v0c0-13.37,11.24-23.99,24.59-23.23l125.85,7.15 V60.62z\"/><path class=\"s\" d=\"M48.39,42.2v10.28l-5.47-1.16v-7.96L48.39,42.2z M63.26,21.92L63.26,21.92c-2.75,0-4.82,2.5-4.31,5.2 l3.33,17.61h1.97l3.33-17.61C68.09,24.42,66.01,21.92,63.26,21.92z M63.26,67.55L63.26,67.55c2.75,0,4.82-2.5,4.31-5.2l-3.33-17.61 h-1.97l-3.33,17.61C58.44,65.05,60.51,67.55,63.26,67.55z M-44.97,43.64L-44.97,43.64c0.76,0.76,1.99,0.76,2.75,0l6.36-6.36 c0.76-0.76,0.76-1.99,0-2.75l0,0c-0.76-0.76-1.99-0.76-2.75,0l-6.36,6.36C-45.72,41.65-45.72,42.88-44.97,43.64z M-34.82,43.64 L-34.82,43.64c0.76,0.76,1.99,0.76,2.75,0l6.36-6.36c0.76-0.76,0.76-1.99,0-2.75l0,0c-0.76-0.76-1.99-0.76-2.75,0l-6.36,6.36 C-35.58,41.65-35.58,42.88-34.82,43.64z M63.26,43.33h-7.74v2.81h7.74V43.33z\"/><path class=\"s\" d=\"M-71.47,62.75v15.73 M-65.61,62.75v22.93\"/> <path class=\"s s1\" d=\"M52.24,60.8l1.72,11.04l19.89,4.4v6.21L38.9,88.39c-8.09,1.37-15.55-4.68-15.87-12.88l-0.51-13.03 M51.24,28.2 L67.16,2.56l-80.25-3.16c-6.16-0.24-12.13,2.16-16.4,6.61l-16.03,16.69\"/><path class=\"s s1\" d=\"M3.89,39.09l39.03,1.83v13.24L3.89,55.98c-4.66,0-8.44-3.78-8.44-8.44C-4.56,42.87-0.78,39.09,3.89,39.09z M-42.74,31.11l-31.49-1.26c-5.73,0-10.75,3.81-12.3,9.33l-0.67,5.36h29.01L-42.74,31.11z M30.03,47.53L30.03,47.53 c0-1.07-0.87-1.94-1.94-1.94h-9c-1.07,0-1.94,0.87-1.94,1.94v0c0,1.07,0.87,1.94,1.94,1.94h9C29.16,49.47,30.03,48.6,30.03,47.53z\"/>", // Sub

         "<path class=\"s\" d=\"M-41.05,64.38H-76.3c-9.83,0-17.79-7.98-17.77-17.8l0.02-7.96l53-31.34V64.38z M-33.49,21.94v36.39l12.96,9.64 c7.01,5.22,15.52,8.03,24.26,8.03h50.54V7.29l-12-2.39C27.98,2.05,13.19,3.4-0.34,8.77L-33.49,21.94z\"/> <path class=\"s\" d=\"M-53.74,49.67l93.8-17.28 M-53.74,96.38h99.86 M-60.37,44.65L-60.37,44.65c0-1.07-0.87-1.94-1.94-1.94h-9 c-1.07,0-1.94,0.87-1.94,1.94v0c0,1.07,0.87,1.94,1.94,1.94h9C-61.24,46.59-60.37,45.72-60.37,44.65z M-60.37,37.78L-60.37,37.78 c0-1.07-0.87-1.94-1.94-1.94h-9c-1.07,0-1.94,0.87-1.94,1.94v0c0,1.07,0.87,1.94,1.94,1.94h9C-61.24,39.72-60.37,38.85-60.37,37.78 z M-33.49,26.33h-7.56v27.92h7.56V26.33z\"/><path class=\"s s1\" d=\"M-0.29,30.83v-9c0-1.07,0.87-1.94,1.94-1.94h0c1.07,0,1.94,0.87,1.94,1.94v9c0,1.07-0.87,1.94-1.94,1.94h0 C0.58,32.77-0.29,31.9-0.29,30.83z M1.47-0.14c-4.66,0-8.44,3.78-8.44,8.44l1.83,39.03H8.08L9.91,8.3 C9.91,3.64,6.13-0.14,1.47-0.14z\"/> <path class=\"s s1\" d=\"M42.26,32.38c-17.67,0-32,14.33-32,32s14.33,32,32,32s32-14.33,32-32S59.94,32.38,42.26,32.38z M42.26,89.98 c-14.14,0-25.6-11.46-25.6-25.6s11.46-25.6,25.6-25.6s25.6,11.46,25.6,25.6S56.4,89.98,42.26,89.98z M-51.74,49.57 c-12.93,0-23.4,10.48-23.4,23.41c0,12.93,10.48,23.4,23.4,23.4s23.4-10.48,23.4-23.4C-28.33,60.05-38.81,49.57-51.74,49.57z M-51.74,91.7c-10.34,0-18.72-8.38-18.72-18.72c0-10.34,8.38-18.72,18.72-18.72s18.72,8.38,18.72,18.72 C-33.01,83.32-41.4,91.7-51.74,91.7z M-46.35,29.02h-14.78l14.4-10.61L-46.35,29.02z M6.8,52.81H-3.49l1.16-5.47h7.96L6.8,52.81z M54.26,20.3l9-3v18.97l-9-3.28 M54.26,53.04l9-3v18.97l-9-3.28\"/>", // Tank

         "<path class=\"s\" d=\"M0.26,93.33h14.33c0,0-0.76-11.46-2.27-32s13.64-76.47,19.95-99.97s-2.52-60.03-32-60.03 s-38.31,36.54-32,60.03s21.46,79.43,19.95,99.97s-2.27,32-2.27,32H0.26\"/><path class=\"s\" d=\"M-12.9,76.57l-47.02,6.06l3.03-18.95l43.64-22.42 M-26.38-18.46l-9.09,14.31v19.33l14.78-10.8 M13.42,76.57 l47.02,6.06l-3.03-18.95L13.77,41.25 M21.22,4.37L36,15.17V-4.15l-9.09-14.31\"/><path class=\"s s1\" d=\"M-33.66,46.63l-1.83,39.03h-13.24l-1.83-39.03c0-4.66,3.78-8.44,8.44-8.44 C-37.44,38.18-33.66,41.96-33.66,46.63z M34.19,46.63l1.83,39.03h13.24l1.83-39.03c0-4.66-3.78-8.44-8.44-8.44 C37.97,38.18,34.19,41.96,34.19,46.63z\"/><path class=\"s s1\" d=\"M-19.18-74.83c1.04,1.8,0.95,17.15,3.03,27c1.51,7.14,4.01,15.92,2.38,18.14c-1.43,1.94-7.59,1.24-9.95-1.37 c-3.41-3.78-4.15-10.56-4.93-16.67C-30.13-59.39-22.35-80.31-19.18-74.83z M-37.94,85.66h-7.96l-1.16,5.47h10.28L-37.94,85.66z M-10.65,93.33l-1.33,8.05H0.26h12.24l-1.33-8.05 M0.26-34.67c0,0,1.82,0,6.12,0s7.45-32,7.04-43S9.28-88.66,0.26-88.66 s-12.75-0.01-13.16,10.99c-0.41,11,2.74,43,7.04,43S0.26-34.67,0.26-34.67z M19.71-74.83c-1.04,1.8-0.95,17.15-3.03,27 c-1.51,7.14-4.01,15.92-2.38,18.14c1.43,1.94,7.59,1.24,9.95-1.37c3.41-3.78,4.15-10.56,4.93-16.67 C30.65-59.39,22.88-80.31,19.71-74.83z M37.3,91.13h10.28l-1.16-5.47h-7.96L37.3,91.13z\"/>" // Cruiser
         ];

    /**
     * @dev Render an SVG of a ship with the specified features.
     */
    function getImage (uint256 lootprintId, uint8 classId, uint8 colorId, uint8 bays, string calldata shipName)
        public
        view
        returns (string memory)
    {

        string memory regStr = uint2str(lootprintId);
        string memory baysStr = uint2str(bays);

        string[15] memory parts;
        parts[0] = "<svg xmlns=\"http://www.w3.org/2000/svg\" preserveAspectRatio=\"xMinYMin meet\" viewBox=\"0 0 600 600\"><style> .s{fill:white;stroke:white;stroke-width:2;stroke-miterlimit:10;fill-opacity:0.1;stroke-linecap:round}.s1{fill-opacity:0.3}.s2{stroke-width:1}.t{ fill:white;font-family:serif;font-size:20px;}.k{font-weight:bold;text-anchor:end;fill:#ddd;}.n{font-size:22px;font-weight:bold;text-anchor:middle}.l{fill:none;stroke:rgb(230,230,230,0.5);stroke-width:1;clip-path:url(#c);}.r{fill:rgba(0,0,0,0.5);stroke:white;stroke-width:3;}.r1{stroke-width: 1} .a{fill:#FFFFFF;fill-opacity:0.1;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}.b{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;} .c{fill:#FFFFFF;fill-opacity:0.2;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;} .d{fill:#FFFFFF;fill-opacity:0.3;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}</style><defs><clipPath id=\"c\"><rect width=\"600\" height=\"600\" /></clipPath></defs><rect width=\"600\" height=\"600\" fill=\"";
        parts[1] = color_codes[colorId];
        parts[2] = "\"/><polyline class=\"l\" points=\"40,-5 40,605 80,605 80,-5 120,-5 120,605 160,605 160,-5 200,-5 200,605 240,605 240,-5 280,-5 280,605 320,605 320,-5 360,-5 360,605 400,605 400,-5 440,-5 440,605 480,605 480,-5 520,-5 520,605 560,605 560,-5 600,-5 600,605\" /><polyline class=\"l\" points=\"-5,40 605,40 605,80 -5,80 -5,120 605,120 605,160 -5,160 -5,200 605,200 605,240 -5,240 -5,280 605,280 605,320 -5,320 -5,360 605,360 605,400 -5,400 -5,440 605,440 605,480 -5,480 -5,520 605,520 605,560 -5,560 -5,600 605,600\" /><rect class=\"r\" x=\"10\" y=\"10\" width=\"580\" height=\"50\" rx=\"15\" /><rect class=\"l r r1\" x=\"-5\" y=\"80\" width=\"285\" height=\"535\" /><text class=\"t n\" x=\"300\" y=\"42\">";
        parts[3] = shipName;
        parts[4] = "</text><text class=\"t k\" x=\"115\" y=\"147\">Reg:</text><text class=\"t\" x=\"125\" y=\"147\">#";
        parts[5] = regStr;
        parts[6] = "</text><text class=\"t k\" x=\"115\" y=\"187\">Class:</text><text class=\"t\" x=\"125\" y=\"187\">";
        parts[7] = class_names[classId];
        parts[8] = "</text><text class=\"t k\" x=\"115\" y=\"227\">Color:</text><text class=\"t\" x=\"125\" y=\"227\">";
        parts[9] = color_names[colorId];
        parts[10] = "</text><text class=\"t k\" x=\"115\" y=\"267\">Bays:</text><text class=\"t\" x=\"125\" y=\"267\">";
        parts[11] = baysStr;
        parts[12] = "</text><g transform=\"translate(440,440)scale(1.2)\">";
        if (classId < 4) {
            parts[13] = ship_images[classId];
        }
        parts[14] = "</g></svg>";

        bytes memory svg0 = abi.encodePacked(parts[0], parts[1], parts[2],
                                             parts[3], parts[4], parts[5],
                                             parts[6], parts[7], parts[8]);
        bytes memory svg1 = abi.encodePacked(parts[9], parts[10], parts[11],
                                             parts[12], parts[13], parts[14]);

        return string(abi.encodePacked("data:image/svg+xml;base64,", Base64.encode(abi.encodePacked(svg0, svg1))));
    }

    /**
     * @dev Encode a key/value pair as a JSON trait property, where the value is a numeric item (doesn't need quotes)
     */
    function encodeAttribute(string memory key, string memory value) internal pure returns (string memory) {
        return string(abi.encodePacked("{\"trait_type\":\"", key,"\",\"value\":",value,"}"));
    }

    /**
     * @dev Encode a key/value pair as a JSON trait property, where the value is a string item (needs quotes around it)
     */
    function encodeStringAttribute(string memory key, string memory value) internal pure returns (string memory) {
        return string(abi.encodePacked("{\"trait_type\":\"", key,"\",\"value\":\"",value,"\"}"));
    }

    /**
     * @dev Render a JSON metadata object of a ship with the specified features.
     */
    function getJSON(uint256 lootprintId, uint8 classId, uint8 colorId, uint8 bays, string calldata shipName)
        public
        view
        returns (string memory) {
        string memory colorName = color_names[colorId];
        string memory svg = getImage(lootprintId, classId, colorId, bays, shipName);
        bytes memory tokenName = abi.encodePacked("Lootprint #", uint2str(lootprintId), ": ", shipName);
        bytes memory json = abi.encodePacked("{",
                                             "\"attributes\":[",
                                             encodeAttribute("Registration #", uint2str(lootprintId)), ",",
                                             encodeStringAttribute("Class", class_names[classId]), ",",
                                             encodeAttribute("Bays", uint2str(bays)), ",",
                                             encodeStringAttribute("Color", colorName),
                                             "],\"name\":\"", tokenName,
                                             "\",\"description\":\"Build Plans for a MoonCat Spacecraft\",\"image\":\"", svg,
                                             "\"}");
        return string(abi.encodePacked('data:application/json;base64,', Base64.encode(json)));

    }

    /* Utilities */

    function uint2str(uint value) internal pure returns (string memory) {
        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);
    }

}

/// [MIT License]
/// @title Base64
/// @notice Provides a function for encoding some bytes in base64
/// @author Brecht Devos <[email protected]>
library Base64 {
    bytes internal constant TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    /// @notice Encodes some bytes to the base64 representation
    function encode(bytes memory data) internal pure returns (string memory) {
        uint256 len = data.length;
        if (len == 0) return "";

        // multiply by 4/3 rounded up
        uint256 encodedLen = 4 * ((len + 2) / 3);

        // Add some extra buffer at the end
        bytes memory result = new bytes(encodedLen + 32);

        bytes memory table = TABLE;

        assembly {
            let tablePtr := add(table, 1)
                let resultPtr := add(result, 32)

                for {
                     let i := 0
                } lt(i, len) {

            } {
            i := add(i, 3)
            let input := and(mload(add(data, i)), 0xffffff)

            let out := mload(add(tablePtr, and(shr(18, input), 0x3F)))
            out := shl(8, out)
            out := add(out, and(mload(add(tablePtr, and(shr(12, input), 0x3F))), 0xFF))
            out := shl(8, out)
            out := add(out, and(mload(add(tablePtr, and(shr(6, input), 0x3F))), 0xFF))
            out := shl(8, out)
            out := add(out, and(mload(add(tablePtr, and(input, 0x3F))), 0xFF))
            out := shl(224, out)

            mstore(resultPtr, out)

            resultPtr := add(resultPtr, 4)
            }

            switch mod(len, 3)
                          case 1 {
                                  mstore(sub(resultPtr, 2), shl(240, 0x3d3d))
                }
            case 2 {
                    mstore(sub(resultPtr, 1), shl(248, 0x3d))
            }

            mstore(result, encodedLen)
                }

        return string(result);
    }
}

// SPDX-License-Identifier: AGPL-3.0

pragma solidity 0.8.1;

interface IMoonCatAcclimator {
    function getApproved(uint256 tokenId) external view returns (address);
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function ownerOf(uint256 tokenId) external view returns (address);
}

interface IMoonCatRescue {
    function rescueOrder(uint256 tokenId) external view returns (bytes5);
    function catOwners(bytes5 catId) external view returns (address);
}

interface IReverseResolver {
    function claim(address owner) external returns (bytes32);
}

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

interface IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function setApprovalForAll(address operator, bool _approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external;
}

interface IERC721Metadata is IERC721 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

interface IERC721Enumerable is IERC721 {
    function totalSupply() external view returns (uint256);
    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256 tokenId);
    function tokenByIndex(uint256 index) external view returns (uint256);
}

interface IERC721Receiver {
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}

interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
}

interface IMoonCatLootprintsMetadata {
    function getJSON(uint256 lootprintId,
                     uint8 classId,
                     uint8 colorId,
                     uint8 bays,
                     string calldata shipName)
        external view returns (string memory);
    function getImage(uint256 lootprintId,
                      uint8 classId,
                      uint8 colorId,
                      uint8 bays,
                      string calldata shipName)
        external view returns (string memory);
    function getClassName(uint8 classId) external view returns (string memory);
    function getColorName(uint8 classId) external view returns (string memory);
}


/**
 * @dev Derived from OpenZeppelin standard template
 * https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol
 * b0cf6fbb7a70f31527f36579ad644e1cf12fdf4e
 */
library EnumerableSet {
    struct Set {
        uint256[] _values;
        mapping (uint256 => uint256) _indexes;
    }

    function at(Set storage set, uint256 index) internal view returns (uint256) {
        return set._values[index];
    }

    function contains(Set storage set, uint256 value) internal view returns (bool) {
        return set._indexes[value] != 0;
    }

    function length(Set storage set) internal view returns (uint256) {
        return set._values.length;
    }

    function add(Set storage set, uint256 value) internal returns (bool) {
        if (!contains(set, value)) {
            set._values.push(value);
            // The value is stored at length-1, but we add 1 to all indexes
            // and use 0 as a sentinel value
            set._indexes[value] = set._values.length;
            return true;
        } else {
            return false;
        }
    }

    function remove(Set storage set, uint256 value) internal returns (bool) {
        // We read and store the value's index to prevent multiple reads from the same storage slot
        uint256 valueIndex = set._indexes[value];
        if (valueIndex != 0) { // Equivalent to contains(set, value)
            // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
            // the array, and then remove the last element (sometimes called as 'swap and pop').
            // This modifies the order of the array, as noted in {at}.
            uint256 toDeleteIndex = valueIndex - 1;
            uint256 lastIndex = set._values.length - 1;
            if (lastIndex != toDeleteIndex) {
                uint256 lastvalue = set._values[lastIndex];
                // Move the last value to the index where the value to delete is
                set._values[toDeleteIndex] = lastvalue;
                // Update the index for the moved value
                set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
            }

            // Delete the slot where the moved value was stored
            set._values.pop();
            // Delete the index for the deleted slot
            delete set._indexes[value];
            return true;
        } else {
            return false;
        }
    }
}

/**
 * @title MoonCat​Lootprints
 * @dev MoonCats have found some plans for building spaceships
 */
contract MoonCatLootprints is IERC165, IERC721Enumerable, IERC721Metadata {

    /* ERC-165 */

    function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165) returns (bool) {
        return (interfaceId == type(IERC721).interfaceId ||
                interfaceId == type(IERC721Metadata).interfaceId ||
                interfaceId == type(IERC721Enumerable).interfaceId);
    }

    /* External Contracts */

    IMoonCatAcclimator MCA = IMoonCatAcclimator(0xc3f733ca98E0daD0386979Eb96fb1722A1A05E69);
    IMoonCatRescue MCR = IMoonCatRescue(0x60cd862c9C687A9dE49aecdC3A99b74A4fc54aB6);
    IMoonCatLootprintsMetadata public Metadata;

    /* Name String Data */

    string[4] internal honorifics =
        [
         "Legendary",
         "Notorious",
         "Distinguished",
         "Renowned"
         ];

    string[32] internal adjectives =
        [
         "Turbo",
         "Tectonic",
         "Rugged",
         "Derelict",
         "Scratchscarred",
         "Purrfect",
         "Rickety",
         "Sparkly",
         "Ethereal",
         "Hissing",
         "Pouncing",
         "Stalking",
         "Standing",
         "Sleeping",
         "Playful",
         "Menancing", // Poor Steve.
         "Cuddly",
         "Neurotic",
         "Skittish",
         "Impulsive",
         "Sly",
         "Ponderous",
         "Prodigal",
         "Hungry",
         "Grumpy",
         "Harmless",
         "Mysterious",
         "Frisky",
         "Furry",
         "Scratchy",
         "Patchy",
         "Hairless"
         ];

    string[15] internal mods =
        [
         "Star",
         "Galaxy",
         "Constellation",
         "World",
         "Moon",
         "Alley",
         "Midnight",
         "Wander",
         "Tuna",
         "Mouse",
         "Catnip",
         "Toy",
         "Kibble",
         "Hairball",
         "Litterbox"
         ];

    string[32] internal mains =
        [
         "Lightning",
         "Wonder",
         "Toebean",
         "Whisker",
         "Paw",
         "Fang",
         "Tail",
         "Purrbox",
         "Meow",
         "Claw",
         "Scratcher",
         "Chomper",
         "Nibbler",
         "Mouser",
         "Racer",
         "Teaser",
         "Chaser",
         "Hunter",
         "Leaper",
         "Sleeper",
         "Pouncer",
         "Stalker",
         "Stander",
         "TopCat",
         "Ambassador",
         "Admiral",
         "Commander",
         "Negotiator",
         "Vandal",
         "Mischief",
         "Ultimatum",
         "Frolic"
         ];

    string[16] internal designations =
        [
         "Alpha",
         "Tau",
         "Pi",
         "I",
         "II",
         "III",
         "IV",
         "V",
         "X",
         "Prime",
         "Proper",
         "1",
         "1701-D",
         "2017",
         "A",
         "Runt"
         ];

    /* Data */

    bytes32[400] ColorTable;

    /* Structs */

    struct Lootprint {
        uint16 index;
        address owner;
    }

    /* State */

    using EnumerableSet for EnumerableSet.Set;

    address payable public contractOwner;

    bool public frozen = true;

    bool public mintingWindowOpen = true;

    uint8 revealCount = 0;

    uint256 public price = 50000000000000000;

    bytes32[100] NoChargeList;

    bytes32[20] revealBlockHashes;

    Lootprint[25600] public Lootprints; // lootprints by lootprintId/rescueOrder

    EnumerableSet.Set internal LootprintIdByIndex;

    mapping(address => EnumerableSet.Set) internal LootprintsByOwner;

    mapping(uint256 => address) private TokenApprovals; // lootprint id -> approved address

    mapping(address => mapping(address => bool)) private OperatorApprovals; // owner address -> operator address -> bool

    /* Modifiers */

    modifier onlyContractOwner () {
        require(msg.sender == contractOwner, "Only Contract Owner");
        _;
    }

    modifier lootprintExists (uint256 lootprintId) {
        require(LootprintIdByIndex.contains(lootprintId), "ERC721: operator query for nonexistent token");
        _;
    }

    modifier onlyOwnerOrApproved(uint256 lootprintId) {
        require(LootprintIdByIndex.contains(lootprintId), "ERC721: query for nonexistent token");
        address owner = ownerOf(lootprintId);
        require(msg.sender == owner || msg.sender == TokenApprovals[lootprintId] || OperatorApprovals[owner][msg.sender],
                "ERC721: transfer caller is not owner nor approved");
        _;
    }

    modifier notFrozen () {
        require(!frozen, "Frozen");
        _;
    }

    /* ERC-721 Helpers */

    function setApprove(address to, uint256 lootprintId) private {
        TokenApprovals[lootprintId] = to;
        emit Approval(msg.sender, to, lootprintId);
    }

    function handleTransfer(address from, address to, uint256 lootprintId) private {
        require(to != address(0), "ERC721: transfer to the zero address");
        setApprove(address(0), lootprintId);
        LootprintsByOwner[from].remove(lootprintId);
        LootprintsByOwner[to].add(lootprintId);
        Lootprints[lootprintId].owner = to;
        emit Transfer(from, to, lootprintId);
    }

    /* ERC-721 */

    function totalSupply() public view override returns (uint256) {
        return LootprintIdByIndex.length();
    }

    function balanceOf(address owner) public view override returns (uint256 balance) {
        return LootprintsByOwner[owner].length();
    }

    function ownerOf(uint256 lootprintId) public view override returns (address owner) {
        return Lootprints[lootprintId].owner;
    }

    function approve(address to, uint256 lootprintId) public override lootprintExists(lootprintId) {
        address owner = ownerOf(lootprintId);
        require(to != owner, "ERC721: approval to current owner");
        require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all");
        setApprove(to, lootprintId);
    }

    function getApproved(uint256 lootprintId) public view override returns (address operator) {
        return TokenApprovals[lootprintId];
    }

    function setApprovalForAll(address operator, bool approved) public override {
        require(operator != msg.sender, "ERC721: approve to caller");
        OperatorApprovals[msg.sender][operator] = approved;
        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function isApprovedForAll(address owner, address operator) public view override returns (bool) {
        return OperatorApprovals[owner][operator];
    }

    function safeTransferFrom(address from, address to, uint256 lootprintId, bytes memory _data) public override onlyOwnerOrApproved(lootprintId) {
        handleTransfer(from, to, lootprintId);
        uint256 size;
        assembly {
            size := extcodesize(to)
        }
        if (size > 0) {
            try IERC721Receiver(to).onERC721Received(msg.sender, from, lootprintId, _data) returns (bytes4 retval) {
                if (retval != IERC721Receiver.onERC721Received.selector) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                }
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        }
    }

    function safeTransferFrom(address from, address to, uint256 lootprintId) public override {
        safeTransferFrom(from, to, lootprintId, "");
    }

    function transferFrom(address from, address to, uint256 lootprintId) public override onlyOwnerOrApproved(lootprintId) {
        handleTransfer(from, to, lootprintId);
    }

    /* ERC-721 Enumerable */

    function tokenByIndex(uint256 index) public view override returns (uint256) {
        return LootprintIdByIndex.at(index);
    }

    function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
        require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
        return LootprintsByOwner[owner].at(index);
    }

    /* Reveal */

    bool pendingReveal = false;
    uint256 revealPrepBlock;
    bytes32 revealSeedHash;

    /**
     * @dev How many lootprints are awaiting being revealed?
     */
    function pendingRevealCount() public view returns (uint256) {
        uint256 numRevealed = revealCount * 2560;
        if (numRevealed > LootprintIdByIndex.length()) return 0;
        return LootprintIdByIndex.length() - numRevealed;
    }

    /**
     * @dev Start a reveal action.
     * The hash submitted here must be the keccak256 hash of a secret number that will be submitted to the next function
     */
    function prepReveal(bytes32 seedHash) public onlyContractOwner {
        require(!pendingReveal && seedHash != revealSeedHash && revealCount < 20, "Prep Conditions Not Met");
        revealSeedHash = seedHash;
        revealPrepBlock = block.number;
        pendingReveal = true;
    }

    /**
     * @dev Finalize a reveal action.
     * Must take place at least one block after the `prepReveal` action was taken
     */
    function reveal(uint256 revealSeed) public onlyContractOwner{
        require(pendingReveal
                && block.number > revealPrepBlock
                && keccak256(abi.encodePacked(revealSeed)) == revealSeedHash
                , "Reveal Conditions Not Met");

        if (block.number - revealPrepBlock < 255) {
            bytes32 blockSeed = keccak256(abi.encodePacked(revealSeed, blockhash(revealPrepBlock)));
            revealBlockHashes[revealCount] = blockSeed;
            revealCount++;
        }
        pendingReveal = false;
    }

    /* Minting */

    /**
     * @dev Is the minting of a specific rescueOrder needing payment or is it free?
     */
    function paidMint(uint256 rescueOrder) public view returns (bool) {
        uint256 wordIndex = rescueOrder / 256;
        uint256 bitIndex = rescueOrder % 256;
        return (uint(NoChargeList[wordIndex] >> (255 - bitIndex)) & 1) == 0;
    }

    /**
     * @dev Create the token
     * Checks that the address minting is the current owner of the MoonCat, and ensures that MoonCat is Acclimated
     */
    function handleMint(uint256 rescueOrder, address to) private {
        require(mintingWindowOpen, "Minting Window Closed");
        require(MCR.catOwners(MCR.rescueOrder(rescueOrder)) == 0xc3f733ca98E0daD0386979Eb96fb1722A1A05E69,
                "Not Acclimated");
        address moonCatOwner = MCA.ownerOf(rescueOrder);
        require((msg.sender == moonCatOwner)
            || (msg.sender == MCA.getApproved(rescueOrder))
            || (MCA.isApprovedForAll(moonCatOwner, msg.sender)),
            "Not AMC Owner or Approved"
        );

        require(!LootprintIdByIndex.contains(rescueOrder), "Already Minted");
        Lootprints[rescueOrder] = Lootprint(uint16(LootprintIdByIndex.length()), to);
        LootprintIdByIndex.add(rescueOrder);
        LootprintsByOwner[to].add(rescueOrder);
        emit Transfer(address(0), to, rescueOrder);
    }

    /**
     * @dev Mint a lootprint, and give it to a specific address
     */
    function mint(uint256 rescueOrder, address to) public payable notFrozen {
        if (paidMint(rescueOrder)) {
            require(address(this).balance >= price, "Insufficient Value");
            contractOwner.transfer(price);
        }
        handleMint(rescueOrder, to);
        if (address(this).balance > 0) {
            // The buyer over-paid; transfer their funds back to them
            payable(msg.sender).transfer(address(this).balance);
        }
    }

    /**
     * @dev Mint a lootprint, and give it to the address making the transaction
     */
    function mint(uint256 rescueOrder) public payable {
        mint(rescueOrder, msg.sender);
    }

    /**
     * @dev Mint multiple lootprints, sending them all to a specific address
     */
    function mintMultiple(uint256[] calldata rescueOrders, address to) public payable notFrozen {
        uint256 totalPrice = 0;
        for (uint i = 0; i < rescueOrders.length; i++) {
            if (paidMint(rescueOrders[i])) {
                totalPrice += price;
            }
            handleMint(rescueOrders[i], to);
        }
        require(address(this).balance >= totalPrice, "Insufficient Value");
        if (totalPrice > 0) {
            contractOwner.transfer(totalPrice);
        }
        if (address(this).balance > 0) {
            // The buyer over-paid; transfer their funds back to them
            payable(msg.sender).transfer(address(this).balance);
        }
    }

    /**
     * @dev Mint multiple lootprints, sending them all to the address making the transaction
     */
    function mintMultiple(uint256[] calldata rescueOrders) public payable {
        mintMultiple(rescueOrders, msg.sender);
    }

    /* Contract Owner */

    constructor(address metadataContract) {
        contractOwner = payable(msg.sender);

        Metadata = IMoonCatLootprintsMetadata(metadataContract);

        // https://docs.ens.domains/contract-api-reference/reverseregistrar#claim-address
        IReverseResolver(0x084b1c3C81545d370f3634392De611CaaBFf8148)
            .claim(msg.sender);
    }

    /**
     * @dev Mint the 160 Hero lootprint tokens, and give them to the contract owner
     */
    function setupHeroShips(bool groupTwo) public onlyContractOwner {
        uint startIndex = 25440;
        if (groupTwo) {
             startIndex = 25520;
        }
        require(Lootprints[startIndex].owner == address(0), "Already Set Up");
        for (uint i = startIndex; i < (startIndex+80); i++) {
            Lootprints[i] = Lootprint(uint16(LootprintIdByIndex.length()), contractOwner);
            LootprintIdByIndex.add(i);
            LootprintsByOwner[contractOwner].add(i);
            emit Transfer(address(0), contractOwner, i);
        }
    }

    /**
     * @dev Update the contract used for image/JSON rendering
     */
    function setMetadataContract(address metadataContract) public onlyContractOwner{
        Metadata = IMoonCatLootprintsMetadata(metadataContract);
    }

    /**
     * @dev Set configuration values for which MoonCat creates which color lootprint when minted
     */
    function setColorTable(bytes32[] calldata table, uint startAt) public onlyContractOwner {
        for (uint i = 0; i < table.length; i++) {
            ColorTable[startAt + i] = table[i];
        }
    }

    /**
     * @dev Set configuration values for which MoonCats need to pay for minting a lootprint
     */
    function setNoChargeList (bytes32[100] calldata noChargeList) public onlyContractOwner {
        NoChargeList = noChargeList;
    }

    /**
     * @dev Set configuration values for how much a paid lootprint costs
     */
    function setPrice(uint256 priceWei) public onlyContractOwner {
        price = priceWei;
    }

    /**
     * @dev Allow current `owner` to transfer ownership to another address
     */
    function transferOwnership (address payable newOwner) public onlyContractOwner {
        contractOwner = newOwner;
    }

    /**
     * @dev Prevent creating lootprints
     */
    function freeze () public onlyContractOwner notFrozen {
        frozen = true;
    }

    /**
     * @dev Enable creating lootprints
     */
    function unfreeze () public onlyContractOwner {
        frozen = false;
    }

    /**
     * @dev Prevent any further minting from happening
     * Checks to ensure all have been revealed before allowing locking down the minting process
     */
    function permanentlyCloseMintingWindow() public onlyContractOwner {
        require(revealCount >= 20, "Reveal Pending");
        mintingWindowOpen = false;
    }

    /* Property Decoders */

    function decodeColor(uint256 rescueOrder) public view returns (uint8) {
        uint256 wordIndex = rescueOrder / 64;
        uint256 nibbleIndex = rescueOrder % 64;
        bytes32 word = ColorTable[wordIndex];
        return uint8(uint(word >> (252 - nibbleIndex * 4)) & 15);
    }

    function decodeName(uint32 seed) internal view returns (string memory) {
        seed = seed >> 8;
        uint index;
        string[9] memory parts;
        //honorific
        index = seed & 15;
        if (index < 8) {
            parts[0] = "The ";
            if (index < 4) {
                parts[1] = honorifics[index];
                parts[2] = " ";
            }
        }
        seed >>= 4;
        //adjective
        if ((seed & 1) == 1) {
            index = (seed >> 1) & 31;
            parts[3] = adjectives[index];
            parts[4] = " ";
        }
        seed >>= 6;
        //mod
        index = seed & 15;
        if (index < 15) {
            parts[5] = mods[index];
        }
        seed >>= 4;
        //main
        index = seed & 31;
        parts[6] = mains[index];
        seed >>= 5;
        //designation
        if ((seed & 1) == 1) {
            index = (seed >> 1) & 15;
            parts[7] = " ";
            parts[8] = designations[index];
        }

        return string(abi.encodePacked(parts[0], parts[1], parts[2],
                                       parts[3], parts[4], parts[5],
                                       parts[6], parts[7], parts[8]));

    }

    function decodeClass(uint32 seed) internal pure returns (uint8) {
        uint class_determiner = seed & 15;
        if (class_determiner < 2) {
            return 0;
        } else if (class_determiner < 5) {
            return 1;
        } else if (class_determiner < 9) {
            return 2;
        } else {
            return 3;
        }
    }

    function decodeBays(uint32 seed) internal pure returns (uint8) {
        uint bay_determiner = (seed >> 4) & 15;

        if (bay_determiner < 3) {
            return 5;
        } else if (bay_determiner < 8) {
            return 4;
        } else {
            return 3;
        }
    }

    uint8 constant internal STATUS_NOT_MINTED = 0;
    uint8 constant internal STATUS_NOT_MINTED_FREE = 1;
    uint8 constant internal STATUS_PENDING = 2;
    uint8 constant internal STATUS_MINTED = 3;

    /**
     * @dev Get detailed traits about a lootprint token
     * Provides trait values in native contract return values, which can be used by other contracts
     */
    function getDetails (uint256 lootprintId)
        public
        view
        returns (uint8 status, string memory class, uint8 bays, string memory colorName, string memory shipName, address tokenOwner, uint32 seed)
    {
        Lootprint memory lootprint = Lootprints[lootprintId];
        colorName = Metadata.getColorName(decodeColor(lootprintId));
        tokenOwner = address(0);
        if (LootprintIdByIndex.contains(lootprintId)) {
            if (revealBlockHashes[lootprint.index / 1280] > 0) {
                seed = uint32(uint256(keccak256(abi.encodePacked(lootprintId, revealBlockHashes[lootprint.index / 1280]))));
                return (STATUS_MINTED,
                        Metadata.getClassName(decodeClass(seed)),
                        decodeBays(seed),
                        colorName,
                        decodeName(seed),
                        lootprint.owner,
                        seed);
            }
            status = STATUS_PENDING;
            tokenOwner = lootprint.owner;
        } else if (paidMint(lootprintId)) {
            status = STATUS_NOT_MINTED;
        } else {
            status = STATUS_NOT_MINTED_FREE;
        }
        return (status, "Unknown", 0, colorName, "?", tokenOwner, 0);
    }

    /* ERC-721 Metadata */

    function name() public pure override returns (string memory) {
        return "MoonCatLootprint";
    }

    function symbol() public pure override returns (string memory) {
        return unicode"📜";
    }

    function tokenURI(uint256 lootprintId) public view override lootprintExists(lootprintId) returns (string memory) {
        Lootprint memory lootprint = Lootprints[lootprintId];
        uint8 colorId = decodeColor(lootprintId);
        if (revealBlockHashes[lootprint.index / 1280] > 0) {
            uint32 seed = uint32(uint256(keccak256(abi.encodePacked(lootprintId, revealBlockHashes[lootprint.index / 1280]))));
            uint8 classId = decodeClass(seed);
            string memory shipName = decodeName(seed);
            uint8 bays = decodeBays(seed);
            return Metadata.getJSON(lootprintId, classId, colorId, bays, shipName);
        } else {
            return Metadata.getJSON(lootprintId, 4, colorId, 0, "?");
        }
    }

    function imageURI(uint256 lootprintId) public view lootprintExists(lootprintId) returns (string memory) {
        Lootprint memory lootprint = Lootprints[lootprintId];
        uint8 colorId = decodeColor(lootprintId);
        if (revealBlockHashes[lootprint.index / 1280] > 0) {
            uint32 seed = uint32(uint256(keccak256(abi.encodePacked(lootprintId, revealBlockHashes[lootprint.index / 1280]))));
            uint8 classId = decodeClass(seed);
            string memory shipName = decodeName(seed);
            uint8 bays = decodeBays(seed);
            return Metadata.getImage(lootprintId, classId, colorId, bays, shipName);
        } else {
            return Metadata.getImage(lootprintId, 4, colorId, 0, "?");
        }
    }

    /* Rescue Tokens */

    /**
     * @dev Rescue ERC20 assets sent directly to this contract.
     */
    function withdrawForeignERC20(address tokenContract)
        public
        onlyContractOwner
    {
        IERC20 token = IERC20(tokenContract);
        token.transfer(contractOwner, token.balanceOf(address(this)));
    }

    /**
     * @dev Rescue ERC721 assets sent directly to this contract.
     */
    function withdrawForeignERC721(address tokenContract, uint256 lootprintId)
        public
        onlyContractOwner
    {
        IERC721(tokenContract).safeTransferFrom(address(this), contractOwner, lootprintId);
    }

}

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

Context size (optional):