Hyfins

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/Hyfins.sol": {
      "content": "// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

// I think the animals in these images really exist and one of them has been visiting me.

import "./HyfinsRenderer.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Base64.sol";

contract Hyfins is ERC721, Ownable, ReentrancyGuard {
    using Base64 for bytes;

    HyfinsRenderer public renderer;
    
    uint256 public constant MAX_SUPPLY = 240;
    uint256 private _totalSupply;
    uint256 public mintPrice = 0.004 ether;
    bool public allowlistMintIsActive = false;
    bool public publicMintIsActive = false;
    
    mapping(address => bool) public allowlist;
    mapping(address => bool) public freeMintList;
    
    // STRUCTS
    struct TraitNames {
        string coloring;
        string ear;
        string tail;
        string pattern;
        string eyes;
        string gem;
        string parentA;
        string parentB;
    }

    // EVENTS
    event AllowlistUpdated(address[] addresses, bool added);
    event FreeMintListUpdated(address[] addresses, bool added);
    event TokenMinted(address indexed minter, uint256 tokenId, uint256 parentATokenId, uint256 parentBTokenId);
    event BalanceWithdrawn(uint256 amount);
    event MintPriceUpdated(uint256 newMintPrice);
    event AllowlistMintActivated();
    event PublicMintActivated();

    // CONSTRUCTOR
    constructor() ERC721("Hyfins", "HYFINS") Ownable(msg.sender) {
        renderer = new HyfinsRenderer();
    }

    // MINTING FUNCTIONS
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }
    
    function activateAllowlistMint() external onlyOwner {
        allowlistMintIsActive = true;
        emit AllowlistMintActivated();
    }

    function activatePublicMint() external onlyOwner {
        publicMintIsActive = true;
        emit PublicMintActivated();
    }

    function setMintPrice(uint256 newMintPrice) external onlyOwner {
        mintPrice = newMintPrice;
        emit MintPriceUpdated(newMintPrice);
    }

    function mint(uint256 parentATokenId, uint256 parentBTokenId) external payable nonReentrant {
        bool isFreeMint = false;

        require(allowlistMintIsActive || publicMintIsActive, "Minting is not active");
        if (!publicMintIsActive) { require(allowlist[msg.sender], "Not on allowlist"); }
        _checkIfAnyTokensAreLeft();

        if (msg.value == 0) { 
            require(freeMintList[msg.sender], "Not on free mint list"); 
            isFreeMint = true;
        } else {
            require(msg.value == mintPrice, "Incorrect ETH amount");
        }

        _checkParents(parentATokenId, parentBTokenId);
        uint256 tokenId = _getTokenId(parentATokenId, parentBTokenId);
        _checkIfTokenHasBeenMinted(tokenId);

        if (isFreeMint) {
            freeMintList[msg.sender] = false;
            address[] memory addressToRemove = new address[](1);
            addressToRemove[0] = msg.sender;
            emit FreeMintListUpdated(addressToRemove, false);
        }

        _totalSupply++;
        _safeMint(msg.sender, tokenId);
        emit TokenMinted(msg.sender, tokenId, parentATokenId, parentBTokenId);
    }

    function bellboyMint(uint256[2][] calldata parentPairsTokenIds) external onlyOwner nonReentrant {
        for (uint256 i = 0; i < parentPairsTokenIds.length; i++) {
            _checkIfAnyTokensAreLeft();
            
            uint256 parentATokenId = parentPairsTokenIds[i][0];
            uint256 parentBTokenId = parentPairsTokenIds[i][1];
            _checkParents(parentATokenId, parentBTokenId);
            
            uint256 tokenId = _getTokenId(parentATokenId, parentBTokenId);
            _checkIfTokenHasBeenMinted(tokenId);

            _totalSupply++;
            _safeMint(msg.sender, tokenId);

            emit TokenMinted(msg.sender, tokenId, parentATokenId, parentBTokenId);
        }
    }

    function _getTokenId(uint256 parentATokenId, uint256 parentBTokenId) internal pure returns (uint256) {
        uint256 tokenId = 16 * parentATokenId + parentBTokenId;
        uint256 numberToSubtract = parentATokenId < parentBTokenId ? parentATokenId + 1 : parentATokenId;
        return tokenId - numberToSubtract;
    }

    function _checkIfAnyTokensAreLeft() internal view {
        require(_totalSupply < MAX_SUPPLY, "All tokens minted");
    }

    function _checkIfTokenHasBeenMinted(uint256 tokenId) internal view {
        require(_ownerOf(tokenId) == address(0), "Token already minted");
    }

    function _checkParents(uint256 parentATokenId, uint256 parentBTokenId) internal pure {
        require(parentATokenId < 16 && parentBTokenId < 16, "First Contact token IDs only go up to 15");
        require(parentATokenId != parentBTokenId, "Must choose two different parents");
    }

    // WITHDRAW FUNCTION
    function withdraw() external onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        require(balance > 0, "No ETH to withdraw");
        
        (bool success, ) = payable(owner()).call{value: balance}("");
        require(success, "ETH withdrawal failed");

        emit BalanceWithdrawn(balance);
    }

    // ALLOWLIST FUNCTIONS
    function addToAllowlist(address[] calldata addresses, bool getsFreeMint) external onlyOwner {
        _checkAddressesLength(addresses);

        for (uint256 i = 0; i < addresses.length; i++) {
            _checkAddressNotZero(addresses[i]);
            require(!allowlist[addresses[i]], "Address already on allowlist");
            allowlist[addresses[i]] = true;
            freeMintList[addresses[i]] = getsFreeMint;
        }

        emit AllowlistUpdated(addresses, true);
        if (getsFreeMint) { emit FreeMintListUpdated(addresses, true); }
    }

    function removeFromAllowlist(address[] calldata addresses) external onlyOwner {
        _checkAddressesLength(addresses);
        uint256 addressesAlsoOnFMLCount = 0;

        for (uint256 i = 0; i < addresses.length; i++) {
            _checkAddressNotZero(addresses[i]);
            require(allowlist[addresses[i]], "Address not on allowlist");
            allowlist[addresses[i]] = false;
            if (freeMintList[addresses[i]]) { addressesAlsoOnFMLCount++; }
        }

        emit AllowlistUpdated(addresses, false);

        address[] memory addressesAlsoOnFML = new address[](addressesAlsoOnFMLCount);
        uint256 fmlIndex = 0;

        for (uint256 i = 0; i < addresses.length; i++) {
            if (freeMintList[addresses[i]]) { 
                freeMintList[addresses[i]] = false;
                addressesAlsoOnFML[fmlIndex] = addresses[i]; 
                fmlIndex++;
            }
        }

        if (addressesAlsoOnFMLCount > 0) { emit FreeMintListUpdated(addressesAlsoOnFML, false); }
    }

    function removeFromFreeMintList(address[] calldata addresses) external onlyOwner {
        _checkAddressesLength(addresses);

        for (uint256 i = 0; i < addresses.length; i++) {
            _checkAddressNotZero(addresses[i]);
            require(freeMintList[addresses[i]], "Address not on free mint list");
            freeMintList[addresses[i]] = false;
        }

        emit FreeMintListUpdated(addresses, false);
    }

    function _checkAddressesLength(address[] calldata addresses) internal pure {
        require(addresses.length > 0, "Must provide at least one address");
        require(addresses.length <= 50, "Batch size too large");
    }

    function _checkAddressNotZero(address addr) internal pure {
        require(addr != address(0), "Address cannot be 0");
    }

    // TOKENURI FUNCTIONS
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_ownerOf(tokenId) != address(0), "Token does not exist");

        HyfinsRenderer.DNA memory dna = renderer.getDNA(tokenId);
        TraitNames memory traitNames = _getTraitNames(dna);
        
        string memory svg = renderer.generateSVG(dna);
        string memory json = _generateJSON(traitNames, svg);
        
        return string(abi.encodePacked(
            "data:application/json;base64,",
            Base64.encode(bytes(json))
        ));
    }

    function _generateJSON(TraitNames memory traitNames, string memory svg) internal pure returns (string memory) {
        return string(abi.encodePacked(
            '{"name": "', traitNames.parentA, traitNames.parentB, '",',
            '"description": "HYFINS ARE REAL",',
            '"attributes": [',
            '{"trait_type": "Coloring", "value": "', traitNames.coloring, '"},',
            '{"trait_type": "Ears", "value": "', traitNames.ear, '"},',
            '{"trait_type": "Tail", "value": "', traitNames.tail, '"},',
            '{"trait_type": "Pattern", "value": "', traitNames.pattern, '"},',
            '{"trait_type": "Eyes", "value": "', traitNames.eyes, '"},',
            '{"trait_type": "Gem", "value": "', traitNames.gem, '"},',
            '{"trait_type": "Parent A", "value": "', traitNames.parentA, '"},',
            '{"trait_type": "Parent B", "value": "', traitNames.parentB, '"}',
            '],',
            '"image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '"',
            '}'
        ));
    }

    function _getTraitNames(HyfinsRenderer.DNA memory dna) internal pure returns (TraitNames memory) {
        TraitNames memory traitNames;

        // COLORINGS
        traitNames.coloring = 
            (dna.coloring == 0) ? "Milk" :
            (dna.coloring == 1) ? "Stone" :
            (dna.coloring == 2) ? "Coyote" :
            (dna.coloring == 3) ? "Ink" :
            (dna.coloring == 4) ? "Sky" :
            (dna.coloring == 5) ? "Blush" :
            (dna.coloring == 6) ? "Potion" :
            (dna.coloring == 7) ? "Bear" :
            (dna.coloring == 8) ? "Martian" :
            (dna.coloring == 9) ? "Navy" :
            (dna.coloring == 10) ? "Moon" :
            (dna.coloring == 11) ? "Star" :
            (dna.coloring == 12) ? "Army" :
            (dna.coloring == 13) ? "Silver" :
            (dna.coloring == 14) ? "Cow" :
            (dna.coloring == 15) ? "Coral mut." :
            (dna.coloring == 16) ? "Jade mut." :
            (dna.coloring == 17) ? "Fox mut." :
            "Energy mut.";

        // EARS
        traitNames.ear = 
            (dna.ears == 0) ? "Fairy" :
            (dna.ears == 1) ? "Owl" :
            (dna.ears == 2) ? "Bat" :
            (dna.ears == 3) ? "Pup" :
            (dna.ears == 4) ? "Cat mut." :
            (dna.ears == 5) ? "Imp mut." :
            (dna.ears == 6) ? "Pantheon mut." :
            "Morph mut.";

        // TAILS
        traitNames.tail = 
            (dna.tail == 0) ? "None" :
            (dna.tail == 1) ? "Fox" :
            (dna.tail == 2) ? "Twist" :
            (dna.tail == 3) ? "Monkey" :
            "Twin";
        
        // PATTERNS
        traitNames.pattern = 
            (dna.pattern == 0) ? "Pure" :
            (dna.pattern == 1) ? "Core" :
            (dna.pattern == 2) ? "Mime" :
            (dna.pattern == 3) ? "Racer" :
            (dna.pattern == 4) ? "Socks" :
            (dna.pattern == 5) ? "Collar" :
            (dna.pattern == 6) ? "Blot" :
            (dna.pattern == 7) ? "Pure mut." :
            (dna.pattern == 8) ? "Core mut." :
            (dna.pattern == 9) ? "Mime mut." :
            (dna.pattern == 10) ? "Mime-Pure mut." :
            (dna.pattern == 11) ? "Racer mut." :
            (dna.pattern == 12) ? "Socks mut." :
            (dna.pattern == 13) ? "Collar mut." :
            (dna.pattern == 14) ? "Blot mut." :
            (dna.pattern == 15) ? "Flipped mut." :
            (dna.pattern == 16) ? "Flipped-Mime mut." :
            (dna.pattern == 17) ? "Flipped-Socks mut." :
            (dna.pattern == 18) ? "Bandit mut." :
            (dna.pattern == 19) ? "Bandit-Pure mut." :
            (dna.pattern == 20) ? "Bandit-Socks mut." :
            (dna.pattern == 21) ? "Bandit-Collar mut." :
            (dna.pattern == 22) ? "Bandit-Separator mut." :
            (dna.pattern == 23) ? "Cheetah mut." :
            (dna.pattern == 24) ? "Cheetah-Pure mut." :
            (dna.pattern == 25) ? "Cheetah-Socks mut." :
            (dna.pattern == 26) ? "Cheetah-Collar mut." :
            (dna.pattern == 27) ? "Vitiligo mut." :
            (dna.pattern == 28) ? "Vitiligo-Pure mut." :
            (dna.pattern == 29) ? "Vitiligo-Collar mut." :
            (dna.pattern == 30) ? "Vitiligo-Separator mut." :
            (dna.pattern == 31) ? "Separator mut." :
            (dna.pattern == 32) ? "Separator-Mime mut." :
            (dna.pattern == 33) ? "Separator-Racer mut." :
            (dna.pattern == 34) ? "Separator-Socks mut." :
            "Separator-Collar mut.";

        // EYES
        traitNames.eyes = 
            (dna.eyes == 0) ? "Yellow" :
            (dna.eyes == 1) ? "Pale Blue" :
            (dna.eyes == 2) ? "Blue" :
            (dna.eyes == 3) ? "Gray" :
            (dna.eyes == 4) ? "Green" :
            (dna.eyes == 5) ? "Yellow mut." :
            (dna.eyes == 6) ? "Blue mut." :
            (dna.eyes == 7) ? "Gray mut." :
            "Green mut.";   

        // GEMS
        traitNames.gem = 
            (dna.gem == 0) ? "Sapphire" :
            (dna.gem == 1) ? "Emerald" :
            "Ruby";

        traitNames.parentA = _getParentName(dna.parentA);
        traitNames.parentB = _getParentName(dna.parentB);

        return traitNames;
    }

    function _getParentName(uint256 parentTokenId) internal pure returns (string memory) {
        string memory letter = parentTokenId < 4 ? "A" : parentTokenId < 8 ? "B" : parentTokenId < 12 ? "C" : "D";
        string memory number = parentTokenId % 4 == 0 ? "1" : parentTokenId % 4 == 1 ? "2" : parentTokenId % 4 == 2 ? "3" : "4";

        return string(abi.encodePacked(letter, number));
    }
}
"
    },
    "src/HyfinsRenderer.sol": {
      "content": "// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/utils/Strings.sol";

contract HyfinsRenderer {
    using Strings for uint256;

    // COLORS
    string constant STONE = "#C3D2D7";
    string constant COYOTE = "#E6D7C3";
    string constant COYOTE_LIGHT = "#FAF0E1";
    string constant COYOTE_DARK = "#8C6E5A";
    string constant INK = "#696E73";
    string constant SKY = "#DCFAFF";
    string constant BLUSH = "#FFDCFA";
    string constant POTION = "#96A0DC";
    string constant POTION_LIGHT = "#D7E1FF";
    string constant BEAR = "#BE967D";
    string constant MARTIAN = "#C3F0C8";
    string constant MARTIAN_LIGHT = "#E6FFEB";
    string constant NAVY = "#6996BE";
    string constant NAVY_LIGHT = "#C8D7E1";
    string constant STAR = "#FFFFBE";
    string constant ARMY = "#A5BE96";
    string constant ARMY_LIGHT = "#D7E1D7";
    string constant CORAL = "#FFA0A0";
    string constant CORAL_LIGHT = "#FFDCDC";
    string constant FOX = "#FFCD9B";
    string constant JADE = "#96D7B9";
    string constant ENERGY = "#D7FFB9";
    string constant YELLOW = "#FFFF9B";
    string constant BLUE = "#82EBFF";
    string constant GRAY = "#F0FFFF";
    string constant GREEN = "#9BFF9B";
    string constant SAPPHIRE = "#4BE1FF";
    string constant EMERALD = "#82FF82";
    string constant RUBY = "#FF9BFF";

    // SVG SNIPPETS
    bytes constant HEADER = '<svg width="208" height="304" viewBox="0 0 52 76" fill="#000" fill-rule="evenodd" shape-rendering="crispEdges" xmlns="http://www.w3.org/2000/svg">';
    bytes constant SCENE = '\
\
<g id="scene">\
<rect x="0" y="0" width="52" height="76" fill="#FFF"/>\
<rect x="2" y="2" width="48" height="74" fill="#DCE6EB"/>\
<path d="M50 72H52V73H0V72H2V71H12V70H40V71H50V72Z"/>\
<path d="M50 73H52V76H0V73H2V72H12V71H40V72H50V73Z" fill="#FFF"/>\
<path d="M37 76H15V75H18V74H34V75H37V76Z" class="shadow"/>\
<path d="M42 40H43V41H42V42H41V41H40V40H41V39H42V40ZM39 35H40V36H39V37H38V36H37V35H38V34H39V35ZM45 35H46V36H45V37H44V36H43V35H44V34H45V35ZM42 30H43V31H42V32H41V31H40V30H41V29H42V30Z" fill="#FFF"/>\
</g>';
    bytes constant TAIL_PREFIX = '\
\
<g id="tail" transform="translate(7,40)">';
    bytes constant TAIL_2_PREFIX = '\
\
<g id="tail-2" transform="translate(45,40) scale(-1,1)">';
    bytes constant FOX_TAIL = '\
<path d="M9 2H8V3H9V2ZM8 3H6V4H8V3ZM6 4H4V5H6V4ZM4 5H3V6H4V5ZM3 6H2V7H3V6ZM5 17H6V16H7V14H8V12H9V11H10V10H11V9H2V7H1V9H0V15H1V17H2V18H5V17Z"/>\
<path d="M9 3H8V4H6V5H4V6H3V7H2V9H1V15H2V17H5V16H6V14H7V12H8V11H9V10H10V7H9V3Z" class="base-color"/>\
<path d="M10 10V7H9V3H8V8H9V10H10Z" class="shadow"/>';
    bytes constant TWIST_TAIL = '\
<path d="M7 13H4V12H3V11H2V7H3V5H4V4H5V5H9V7H7V8H6V11H7V13ZM7 4H5V3H7V4ZM9 3H7V2H9V3Z"/>\
<path d="M6 12H4V11H3V7H4V5H5V4H7V3H9V6H7V7H6V8H5V11H6V12Z" class="base-color"/>\
<rect x="8" y="3" width="1" height="3" class="shadow"/>';
    bytes constant MONKEY_TAIL = '\
<path d="M9 5H8V6H9V5ZM8 6H7V7H8V6ZM6 23H7V21H6V19H5V15H6V13H7V11H8V10H9V9H10V8H7V7H6V8H5V9H4V10H3V12H2V14H1V20H2V22H3V23H4V24H6V23Z"/>\
<path d="M9 6H8V7H7V8H6V9H5V10H4V12H3V14H2V20H3V22H4V23H6V21H5V19H4V15H5V13H6V11H7V10H8V9H9V8H10V7H9V6Z" class="base-color"/>\
<path d="M8 8H10V7H9V6H8V8Z" class="shadow"/>';
    bytes constant BODY = '\
\
<g id="body" transform="translate(14,21)">\
<path d="M20 43H15V42H14V41H13V31H14V29H10V31H11V41H10V42H9V43H4V42H3V41H2V36H3V31H4V28H3V26H2V21H3V19H4V18H5V14H6V13H7V11H8V10H6V9H5V8H2V7H1V5H0V0H24V5H23V7H22V8H19V9H18V10H16V11H17V13H18V14H19V18H20V19H21V21H22V26H21V28H20V31H21V36H22V41H21V42H20V43Z"/>\
<path d="M10 21H8V19H7V21H8V22H10V21H11V18H10V21ZM16 21H14V18H15V16H14V18H13V21H14V22H16V21H17V19H18V18H19V19H20V21H21V26H20V28H19V31H20V36H21V41H20V42H19V40H18V42H17V40H16V42H15V41H14V31H15V25H14V28H10V25H9V31H10V41H9V42H8V40H7V42H6V40H5V42H4V41H3V36H4V31H5V28H4V26H3V21H4V19H5V18H6V19H7V17H6V14H7V13H8V11H16V13H17V14H18V17H17V19H16V21ZM9 18H10V16H9V18ZM23 5H22V7H19V8H18V9H16V10H8V9H6V8H5V7H2V5H1V0H23V5Z" class="base-color"/>\
<path d="M5 32H9V31H10V33H9V34H5V33H4V31H5V32ZM15 32H19V31H20V33H19V34H15V33H14V31H15V32ZM14 28H10V27H14V28ZM10 23H8V22H10V23ZM16 23H14V22H16V23ZM8 22H7V21H8V22ZM11 22H10V21H11V22ZM14 22H13V21H14V22ZM17 22H16V21H17V22ZM7 21H6V19H7V21ZM18 21H17V19H18V21ZM16 12H15V13H9V12H8V11H16V12Z" class="shadow"/>\
</g>';
    bytes constant EARS_PREFIX = '\
\
<g id="ears" transform="translate(11,9)">';
    bytes constant FAIRY_EARS = '\
<path d="M28 12H2V11H28V12ZM3 4H10V5H12V4H18V5H20V4H27V3H29V4H30V9H29V11H28V9H2V11H1V9H0V4H1V3H3V4Z"/>\
<path d="M27 12H3V11H2V9H1V4H3V5H10V6H12V5H18V6H20V5H27V4H29V9H28V11H27V12Z" class="base-color"/>';
    bytes constant OWL_EARS = '\
<path d="M27 12H3V11H27V12ZM4 1H5V2H6V3H7V4H9V5H12V4H18V5H21V4H23V3H24V2H25V1H26V0H27V1H28V6H29V9H28V11H27V9H3V11H2V9H1V6H2V1H3V0H4V1Z"/>\
<path d="M26 12H4V11H3V9H2V6H3V7H4V6H3V1H4V2H5V3H6V4H7V5H9V6H12V5H18V6H21V5H23V4H24V3H25V2H26V1H27V6H26V7H25V8H26V7H27V6H28V9H27V11H26V12ZM4 8H5V7H4V8Z" class="base-color"/>';
    bytes constant BAT_EARS = '\
<path d="M4 0H5V1H6V2H7V4H8V6H9V5H12V4H18V5H21V6H22V4H23V2H24V1H25V0H26V1H27V2V4V6V7V12H26V7H4V12H3V7V6V4V2V1H4V0Z"/>\
<path d="M5 2H6V4H7V6H8V7H9V6H12V5H18V6H21V7H22V6H23V4H24V2H25V1H26V12H4V1H5V2Z" class="base-color"/>';
    bytes constant PUP_EARS = '\
<path d="M4 3H7V4H9V5H12V4H18V5H21V4H23V3H26V4H28V5H29V6H30V9H28V11H27V9H3V11H2V9H0V6H1V5H2V4H4V3ZM3 11H4V12H3V11ZM27 11V12H26V11H27Z"/>\
<path d="M26 12H4V11H3V8H1V6H2V5H4V4H7V5H9V6H12V5H18V6H21V5H23V4H26V5H28V6H29V8H27V11H26V12Z" class="base-color"/>';
    bytes constant CAT_EARS = '\
<path d="M6 3H8V4H9V5H12V4H18V5H21V4H22V3H24V2H27V3H28V11H27V12H3V11H2V3H3V2H6V3Z"/>\
<path d="M6 4H8V5H9V6H12V5H18V6H21V5H22V4H24V3H27V11H26V12H4V11H3V3H6V4Z" class="base-color"/>';
    bytes constant IMP_EARS = '\
<path d="M5 2H6V4H8V5H12V4H18V5H22V4H24V2H25V1H26V2H27V4H28V11H27V12H3V11H2V4H3V2H4V1H5V2Z"/>\
<path d="M26 12H4V11H3V4H4V2H5V4H6V5H8V6H12V5H18V6H22V5H24V4H25V2H26V4H27V11H26V12Z" class="base-color"/>';
    bytes constant PANTHEON_EARS = '\
<path d="M7 3H8V6H9V5H12V4H18V5H21V6H22V3H23V2H26V3H27V12H3V3H4V2H7V3Z"/>\
<path d="M7 6H8V7H9V6H12V5H18V6H21V7H22V6H23V3H26V12H4V3H7V6Z" class="base-color"/>';
    bytes constant MORPH_EARS = '\
<path d="M10 1H11V3H10V5H12V4H18V5H20V3H19V1H20V0H23V1H25V2H26V3H27V5H28V11H27V12H3V11H2V5H3V3H4V2H5V1H7V0H10V1Z"/>\
<path d="M10 3H9V5H10V6H12V5H18V6H20V5H21V3H20V1H23V2H25V3H26V5H27V11H26V12H4V11H3V5H4V3H5V2H7V1H10V3Z" class="base-color"/>';
    bytes constant EYES = '\
\
<g id="eyes" transform="translate(17,20)">\
<path d="M6 2H7V6H6V7H2V6H1V5H0V1H1V0H5V1H6V2ZM17 1H18V5H17V6H16V7H12V6H11V2H12V1H13V0H17V1Z"/>\
<path d="M5 2H6V6H2V5H1V1H5V2ZM17 5H16V6H12V2H13V1H17V5Z" class="eye-color"/>\
<path d="M2 3H1V2H2V3ZM17 3H16V2H17V3ZM3 2H2V1H3V2ZM16 2H15V1H16V2Z" class="shadow"/>\
<path d="M2 2H1V1H2V2ZM17 1V2H16V1H17Z" fill="#FFF"/>\
</g>';
    bytes constant GEM = '\
\
<g id="gem" transform="translate(24,16)">\
<path d="M3 1H4V3H3V4H1V3H0V1H1V0H3V1Z"/>\
<rect x="1" y="1" width="2" height="2" class="gem-color"/>\
<path d="M3 3H2V2H3V3ZM2 2H1V1H2V2Z" class="shadow"/>\
<rect x="2" y="1" width="1" height="1" fill="#FFF"/>\
</g>';
    bytes constant TAIL_PATTERN_PREFIX = '\
\
<g id="tail-pattern" transform="translate(7,40)">';
    bytes constant TAIL_2_PATTERN_PREFIX = '\
\
<g id="tail-2-pattern" transform="translate(45,40) scale(-1,1)">';
    bytes constant COLLAR_FOX_TAIL_PATTERN = '\
<path d="M5 5H4V6H5V5ZM6 6H5V7H6V6ZM7 7H6V8H7V7ZM8 8H7V9H8V8ZM9 9H8V10H9V9ZM2 11H1V12H2V11ZM4 12H2V13H4V12ZM6 13H4V14H6V13Z"/>\
<path d="M2 13H4V14H6V16H5V17H2V15H1V12H2V13ZM10 10H9V9H8V8H7V7H6V6H5V5H6V4H8V3H9V7H10V10Z" class="pattern-color"/>\
<path d="M9 7H10V10H9V8H8V3H9V7Z" class="shadow"/>';
    bytes constant COLLAR_TWIST_TAIL_PATTERN = '\
<rect x="4" y="7" width="1" height="1"/>\
<path d="M6 12H4V11H3V7H4V8H5V11H6V12Z" class="pattern-color"/>';
    bytes constant COLLAR_MONKEY_TAIL_PATTERN = '\
<path d="M7 9H6V10H7V9ZM4 14H3V20H4V14Z"/>\
<path d="M9 6H8V7H7V8H6V9H7V10H8V9H9V8H10V7H9V6ZM3 19H4V15H3V14H2V20H3V19Z" class="pattern-color"/>\
<path d="M10 8H8V6H9V7H10V8Z" class="shadow"/>';   
    bytes constant FACE_PATTERN_PREFIX = '\
\
<g id="face-pattern" transform="translate(14,14)">';
    bytes constant MIME_FACE_PATTERN = '\
<path d="M22 5H23V6H24V7H0V6H1V5H2V4H3V3H21V4H22V5Z"/>\
<path d="M22 6H23V12H22V14H19V15H18V16H16V17H8V16H6V15H5V14H2V12H1V6H2V5H3V4H21V5H22V6Z" class="pattern-color"/>';
    bytes constant RACER_FACE_PATTERN = '\
<path d="M17 16H7V1H17V16Z"/>\
<path d="M16 17H8V1H9V0H15V1H16V17Z" class="pattern-color"/>';
    bytes constant BANDIT_FACE_PATTERN = '\
<path d="M22 12H2V11H22V12ZM14 10H10V9H14V10Z"/>\
<path d="M14 12H20V11H21V12H22V14H19V15H18V16H16V17H8V16H6V15H5V14H2V12H3V11H4V12H10V10H14V12Z" class="pattern-color"/>';
    bytes constant CHEETAH_FACE_PATTERN = '\
<path d="M9 17H8V13H9V17ZM16 13V17H15V13H16ZM3 8H1V7H3V8ZM23 8H21V7H23V8Z"/>\
<path d="M3 11H4V12H5V13H8V16H6V15H5V14H2V12H1V8H3V11ZM16 13H19V12H20V11H21V8H23V12H22V14H19V15H18V16H16V13Z" class="pattern-color"/>';
    bytes constant VITILIGO_FACE_PATTERN = '\
<path d="M12 17H11V14H12V17ZM11 12V14H10V12H11ZM3 9H1V8H3V9Z"/>\
<path d="M8 16H6V15H5V14H2V12H1V9H10V14H11V17H8V16Z" class="pattern-color"/>';
    bytes constant COLLAR_NECK_PATTERN = '\
\
<g id="neck-pattern" transform="translate(22,32)">\
<path d="M6 4H2V3H0V2H8V3H6V4Z"/>\
<path d="M6 3H2V2H0V0H8V2H6V3Z" class="pattern-color"/>\
<path d="M6 2H2V1H1V0H7V1H6V2Z" class="shadow"/>\
</g>';
    bytes constant STOMACH_PATTERN_PREFIX = '\
\
<g id="stomach-pattern" transform="translate(22,32)">';
    bytes constant STOMACH_PATTERN = '\
<path d="M6 4H2V5H6V4ZM8 11H0V14H8V11Z"/>\
<path d="M2 5H6V7H5V10H6V11H7V14H6V17H2V14H1V11H2V10H3V7H2V5Z" class="pattern-color"/>\
<path d="M6 17H2V16H6V17ZM2 12H1V11H2V12ZM7 12H6V11H7V12ZM3 11H2V10H3V11ZM6 11H5V10H6V11Z" class="shadow"/>';
    bytes constant SEPARATOR_STOMACH_PATTERN = '\
<path d="M6 14H2V12H1V11H7V12H6V14ZM6 1H7V3H6V5H2V3H1V1H2V0H6V1Z"/>\
<path d="M6 3H5V5H6V7H5V10H6V12H5V14H6V17H2V14H3V12H2V10H3V7H2V5H3V3H2V1H3V0H5V1H6V3Z" class="pattern-color"/>\
<path d="M6 17H2V16H6V17ZM3 11H2V10H3V11ZM6 11H5V10H6V11ZM5 1H6V2H2V1H3V0H5V1Z" class="shadow"/>';
    bytes constant FOOT_PATTERN_PREFIX = '\
\
<g id="foot-pattern" transform="translate(17,56)">';
    bytes constant SOCKS_FOOT_PATTERN = '\
<path d="M6 3H7V4H0V3H1V2H6V3ZM18 4H11V3H12V2H17V3H18V4Z"/>\
<path d="M6 4H7V6H6V7H5V5H4V7H3V5H2V7H1V6H0V4H1V3H6V4ZM17 7H16V5H15V7H14V5H13V7H12V6H11V4H12V3H17V4H18V6H17V7Z" class="pattern-color"/>';
    bytes constant VITILIGO_FOOT_PATTERN = '\
<path d="M7 3H6V2H7V3ZM6 2H4V1H6V2ZM4 1H1V0H4V1Z"/>\
<path d="M6 7H5V5H4V7H3V5H2V7H1V6H0V1H4V2H6V3H7V6H6V7Z" class="pattern-color"/>';
    bytes constant EAR_PATTERN_PREFIX = '\
\
<g id="ear-pattern" transform="translate(30,10)">';
    bytes constant FAIRY_EAR_PATTERN = '\
<path d="M0 5H1V8H0V5ZM1 8H2V10H1V8ZM6 15H5V16H6V15Z"/>\
<path d="M10 3H8V4H1V8H2V10H4V11H5V15H6V16H7V15V11H8V10H9V8H10V4V3Z" class="pattern-color" id="YWR2YW5jZSB8IHJlcGVhdCB8IGZhdm9yaXRl"/>';
    bytes constant OWL_EAR_PATTERN = '\
<path d="M6 16H5V15H6V16ZM2 10H1V8H2V10ZM1 8H0V5H1V8Z"/>\
<path d="M7 16H6V15H5V11H4V10H2V8H1V5H2V4H4V3H5V2H6V1H7V0H8V5H7V6H6V7H7V6H8V5H9V8H8V10H7V16Z" class="pattern-color" id="Y2Vuc3VzIHwgZXRlcm5hbCB8IHZlbnVl"/>';
    bytes constant BAT_EAR_PATTERN = '\
<path d="M0 5H1V8H0V5ZM1 8H2V10H1V8ZM6 15H5V16H6V15Z"/>\
<path d="M6 0H7V1V3V5V6V8V10V11V15V16H6V15H5V11H4V10H2V8H1V6V5H2V6H3V5H4V3H5V1H6V0Z" class="pattern-color" id="bXl0aCB8IGluY2x1ZGUgfCBzdWJtaXQ="/>';
    bytes constant PUP_EAR_PATTERN = '\
<path d="M0 5H1V8H0V5ZM1 8H2V10H1V8ZM6 15H5V16H6V15Z"/>\
<path d="M7 3H4V4H2V5H1V7V8H2V10H4V11H5V15H6V16H7V15V11V10H8V8V7H10V5H9V4H7V3Z" class="pattern-color" id="Zmxhdm9yIHwgamFndWFyIHwgcmF3"/>';
    bytes constant MORPH_EAR_PATTERN = '\
<path d="M6 16H5V15H6V16ZM2 10H1V8H2V10ZM1 8H0V5H1V8Z"/>\
<path d="M6 2H7V4H8V10H7V16H6V15H5V11H4V10H2V8H1V4H2V2H1V0H4V1H6V2Z" class="pattern-color"/>';

    // GRID COORDINATES
    bytes16 constant GRID_X_COORDS = hex"06060606040200000000020404040202";
    bytes16 constant GRID_Y_COORDS = hex"06040200000000020406060604020204";
    
    // STRUCTS
    struct DNA {
        uint256 coloring;
        uint256 ears;
        uint256 tail;
        uint256 pattern;
        uint256 eyes;
        uint256 gem;
        uint256 parentA;
        uint256 parentB;
    }
    
    // FUNCTIONS
    function getDNA(uint256 tokenId) external pure returns (DNA memory) {
        DNA memory dna;

        // PARENTS
        dna.parentA = tokenId / 15;
        dna.parentB = tokenId % 15;
        dna.parentB = dna.parentA > dna.parentB ? dna.parentB : (((dna.parentB + 1) % 15 == 0) ? 15 : dna.parentB + 1);

        // COLORING, PATTERN, EYES, GEM
        (dna.coloring, dna.pattern, dna.eyes, dna.gem) = 
            (dna.parentA == 0) ? (0, 0, 1, 0) : // Milk
            (dna.parentA == 1) ? (1, 0, 0, 0) : // Stone
            (dna.parentA == 2) ? (2, 0, 0, 1) : // Coyote
            (dna.parentA == 3) ? (3, 0, 0, 1) : // Ink
            (dna.parentA == 4) ? (4, 1, 2, 2) : // Sky
            (dna.parentA == 5) ? (5, 1, 0, 2) : // Blush
            (dna.parentA == 6) ? (6, 1, 0, 2) : // Potion
            (dna.parentA == 7) ? (7, 1, 0, 1) : // Bear
            (dna.parentA == 8) ? (8, 2, 0, 2) : // Martian
            (dna.parentA == 9) ? (9, 2, 0, 0) : // Navy
            (dna.parentA == 10) ? (10, 2, 3, 0) : // Moon
            (dna.parentA == 11) ? (7, 2, 0, 1) : // Bear
            (dna.parentA == 12) ? (11, 3, 0, 0) : // Star
            (dna.parentA == 13) ? (12, 4, 0, 1) : // Army
            (dna.parentA == 14) ? (13, 5, 3, 0) : // Silver
            (14, 6, 4, 2); // Cow

        // EARS
        dna.ears = 
            (dna.parentB % 4 == 0) ? 0 : // Fairy
            (dna.parentB % 4 == 1) ? 1 : // Owl
            (dna.parentB % 4 == 2) ? 2 : // Bat
            3; // Pup


        // TAIL
        dna.tail = 
            (dna.parentB % 16 < 4) ? 0 : // None
            (dna.parentB % 16 < 8) ? 1 : // Fox
            (dna.parentB % 16 < 12) ? 2 : // Twist
            (dna.parentB % 16 == 12) ? 4 : // Twin
            3; // Monkey


        // MUTATE COLORING
        dna.coloring = 
            (tokenId == 12 || tokenId == 39 || tokenId == 118 || tokenId == 150 || tokenId == 206) ? 15 : // Coral
            (tokenId == 19 || tokenId == 59 || tokenId == 85 || tokenId == 215) ? 16 : // Jade
            (tokenId == 3 || tokenId == 78 || tokenId == 188) ? 17 : // Fox
            (tokenId == 45 || tokenId == 228) ? 18 : // Energy
            dna.coloring;

        // MUTATE EARS
        dna.ears = 
            (tokenId == 19 || tokenId == 65 || tokenId == 86 || tokenId == 113 || tokenId == 174 || tokenId == 226) ? 4 : // Cat
            (tokenId == 13 || tokenId == 121 || tokenId == 164 || tokenId == 198 || tokenId == 221) ? 5 : // Imp
            (tokenId == 35 || tokenId == 148 || tokenId == 194) ? 6 : // Pantheon
            (tokenId == 45 || tokenId == 98) ? 7 : // Morph
            dna.ears;

        // MUTATE PATTERN
        dna.pattern = 
            (tokenId == 60 || tokenId == 108 || tokenId == 122 || tokenId == 226) ? 7 : // Pure
            (tokenId == 21 || tokenId == 33 || tokenId == 140 || tokenId == 216) ? 8 : // Core
            (tokenId == 84 || tokenId == 97 || tokenId == 191) ? 9 : // Mime
            (tokenId == 38) ? 10 : // Mime-Pure
            (tokenId == 26) ? 11 : // Racer
            (tokenId == 177) ? 12 : // Socks
            (tokenId == 163) ? 13 : // Collar
            (tokenId == 44) ? 14 : // Blot
            (tokenId == 67 || tokenId == 82 || tokenId == 91 || tokenId == 113) ? 15 : // Flipped
            (tokenId == 164) ? 16 : // Flipped-Mime
            (tokenId == 195) ? 17 : // Flipped-Socks
            (tokenId == 3 || tokenId == 65 || tokenId == 79 || tokenId == 92 || tokenId == 119 || tokenId == 131 || tokenId == 135 || tokenId == 156 || tokenId == 178 || tokenId == 190 || tokenId == 235) ? 18 : // Bandit
            (tokenId == 23 || tokenId == 37) ? 19 : // Bandit-Pure
            (tokenId == 196) ? 20 : // Bandit-Socks
            (tokenId == 217) ? 21 : // Bandit-Collar
            (tokenId == 228) ? 22 : // Bandit-Separator
            (tokenId == 12 || tokenId == 70 || tokenId == 83 || tokenId == 101 || tokenId == 105 || tokenId == 123 || tokenId == 148 || tokenId == 151 || tokenId == 172 || tokenId == 192 || tokenId == 238) ? 23 : // Cheetah
            (tokenId == 28 || tokenId == 32) ? 24 : // Cheetah-Pure
            (tokenId == 203) ? 25 : // Cheetah-Socks
            (tokenId == 220) ? 26 : // Cheetah-Collar
            (tokenId == 45 || tokenId == 61 || tokenId == 89 || tokenId == 96 || tokenId == 114 || tokenId == 145 || tokenId == 161 || tokenId == 173 || tokenId == 180 || tokenId == 201 || tokenId == 229) ? 27 : // Vitiligo
            (tokenId == 16 || tokenId == 34 || tokenId == 52) ? 28 : // Vitiligo-Pure
            (tokenId == 223) ? 29 : // Vitiligo-Collar
            (tokenId == 128) ? 30 : // Vitiligo-Separator
            (tokenId == 18 || tokenId == 43 || tokenId == 59 || tokenId == 71 || tokenId == 77 || tokenId == 98 || tokenId == 110) ? 31 : // Separator
            (tokenId == 126 || tokenId == 142 || tokenId == 160 || tokenId == 166) ? 32 : // Separator-Mime
            (tokenId == 187) ? 33 : // Separator-Racer
            (tokenId == 209) ? 34 : // Separator-Socks
            (tokenId == 224) ? 35 : // Separator-Collar
            dna.pattern;

        // MUTATE EYES
        dna.eyes = 
            (tokenId == 1 || tokenId == 3 || tokenId == 12 || tokenId == 150 || tokenId == 158 || tokenId == 160 || tokenId == 210 || tokenId == 215 || tokenId == 219 || tokenId == 228 || tokenId == 230 || tokenId == 236) ? 5 : // Yellow
            (tokenId == 14 || tokenId == 46 || tokenId == 136 || tokenId == 227 || tokenId == 232) ? 6 : // Blue    
            (tokenId == 17 || tokenId == 20 || tokenId == 49 || tokenId == 58 || tokenId == 87 || tokenId == 99 || tokenId == 101 || tokenId == 137 || tokenId == 147 ||tokenId == 181 || tokenId == 186 || tokenId == 197 || tokenId == 207) ? 7 : // Gray
            (tokenId == 7 || tokenId == 55 || tokenId == 134 || tokenId == 198) ? 8 : // Green
            dna.eyes;

        // SWITCH GEM
        dna.gem = 
            (tokenId == 78) ? 0 : // Sapphire
            (tokenId == 12 || tokenId == 19 || tokenId == 85 || tokenId == 150 || tokenId == 215) ? 1 : // Emerald
            (tokenId == 45 || tokenId == 228) ? 2 : // Ruby
            dna.gem;

        return dna;
    }

    function generateSVG(DNA memory dna) external pure returns (string memory) {
        bytes memory grid = _generateGrid(1, dna.parentA);
        bytes memory grid2 = _generateGrid(2, dna.parentB);
        (string memory baseColor, string memory patternColor) = _getCoatColors(dna.coloring, dna.pattern);
        string memory eyeColor = _getEyeColor(dna.eyes);
        string memory gemColor = _getGemColor(dna.gem);
        bytes memory tail = _getTail(dna.tail);
        bytes memory pattern = _getPattern(dna.tail, dna.ears, dna.pattern);
        bytes memory ears = _getEars(dna.ears);

        bytes memory classes = abi.encodePacked(
            '\
<defs>',
            '\
<style>',
            '\
.shadow { fill: #000; fill-opacity: 0.25; }',
            '\
.base-color { fill: ', baseColor, '; }',
            '\
.pattern-color { fill: ', patternColor, '; }',
            '\
.eye-color { fill: ', eyeColor, '; }',
            '\
.gem-color { fill: ', gemColor, '; }',
            '\
</style>',
            '\
</defs>'
        );
        
        return string(abi.encodePacked(
            HEADER,
            classes,
            SCENE,
            grid,
            grid2,
            tail,
            BODY,
            ears,
            pattern,
            EYES,
            GEM,
            '\
</svg>'
        ));
    }

    function _generateGrid(uint256 parent, uint256 parentTokenId) internal pure returns (bytes memory) {
        bytes memory result = abi.encodePacked('\
\
<g id="grid', bytes(parent.toString()), '" transform="translate(', ((parent == 1) ? '4' : '41'), ',4)" fill="', INK, '">');

        for (uint256 i = 0; i < 16; i++) {
            if (i != parentTokenId) {
                uint256 x;
                uint256 y;
                assembly {
                    x := byte(i, GRID_X_COORDS)
                    y := byte(i, GRID_Y_COORDS)
                }
                result = abi.encodePacked(
                    result,
                    '\
<rect x="', x.toString(), '" y="', y.toString(), '" width="1" height="1"/>'
                );
            }
        }

        result = abi.encodePacked(result, '\
</g>');
        return result;
    }

    function _getTail(uint256 tailDNA) internal pure returns (bytes memory) {    
        bytes memory tail = 
            (tailDNA == 0) ? bytes("") :
            (tailDNA == 1) ? FOX_TAIL :
            (tailDNA == 2) ? TWIST_TAIL :
            MONKEY_TAIL;

        tail = tail.length != 0 ? abi.encodePacked(TAIL_PREFIX,tail, '\
</g>') : tail;

        bytes memory tail2 = (tailDNA == 4) ? abi.encodePacked(TAIL_2_PREFIX, MONKEY_TAIL, '\
</g>') : bytes("");

        return abi.encodePacked(
            tail,
            tail2
        );
    }

    function _getEars(uint256 earDNA) internal pure returns (bytes memory) {
        bytes memory ears = 
            (earDNA == 0) ? FAIRY_EARS :
            (earDNA == 1) ? OWL_EARS :
            (earDNA == 2) ? BAT_EARS :
            (earDNA == 3) ? PUP_EARS :
            (earDNA == 4) ? CAT_EARS :
            (earDNA == 5) ? IMP_EARS :
            (earDNA == 6) ? PANTHEON_EARS :
            MORPH_EARS;

        ears = abi.encodePacked(EARS_PREFIX, ears, '\
</g>');
        return ears;
    }

    function _getPattern(uint256 tailDNA, uint256 earDNA, uint256 patternDNA) internal pure returns (bytes memory) {
        bytes memory tail = 
            (patternDNA == 5 || patternDNA == 13 || patternDNA == 21 || patternDNA == 26 || patternDNA == 29 || patternDNA == 35) ? 
            ((tailDNA == 0) ? bytes("") :
            (tailDNA == 1) ? COLLAR_FOX_TAIL_PATTERN :
            (tailDNA == 2) ? COLLAR_TWIST_TAIL_PATTERN :
            COLLAR_MONKEY_TAIL_PATTERN) :
            bytes("");

        tail = tail.length != 0 ? abi.encodePacked(TAIL_PATTERN_PREFIX, tail, '\
</g>') : bytes("");

        bytes memory tail2 = (tailDNA == 4 && patternDNA == 5) ? abi.encodePacked(TAIL_2_PATTERN_PREFIX, COLLAR_MONKEY_TAIL_PATTERN, '\
</g>') : bytes("");
        
        bytes memory ear =
            (patternDNA == 6 || patternDNA == 14 ||patternDNA == 27 || patternDNA == 28 || patternDNA == 29 || patternDNA == 30) ?  
            (earDNA == 0) ? FAIRY_EAR_PATTERN :
            (earDNA == 1) ? OWL_EAR_PATTERN :
            (earDNA == 2) ? BAT_EAR_PATTERN :
            (earDNA == 3) ? PUP_EAR_PATTERN :
            MORPH_EAR_PATTERN : 
            bytes("");

        ear = ear.length != 0 ? abi.encodePacked(EAR_PATTERN_PREFIX, ear, '\
</g>') : bytes("");

        bytes memory face = 
            (patternDNA == 2 || patternDNA == 9 || patternDNA == 10 || patternDNA == 16 || patternDNA == 32) ? MIME_FACE_PATTERN :
            (patternDNA == 3 || patternDNA == 11 || patternDNA == 33) ? RACER_FACE_PATTERN :
            (patternDNA == 18 || patternDNA == 19 || patternDNA == 20 || patternDNA == 21 || patternDNA == 22) ? BANDIT_FACE_PATTERN :
            (patternDNA == 23 || patternDNA == 24 || patternDNA == 25 || patternDNA == 26) ? CHEETAH_FACE_PATTERN :
            (patternDNA == 27 || patternDNA == 28 || patternDNA == 29 || patternDNA == 30) ? VITILIGO_FACE_PATTERN :
            bytes("");

        face = face.length != 0 ? abi.encodePacked(FACE_PATTERN_PREFIX, face, '\
</g>') : bytes("");

        bytes memory neck = (patternDNA == 5 || patternDNA == 13 || patternDNA == 21 || patternDNA == 26 || patternDNA == 29) ? COLLAR_NECK_PATTERN : bytes("");

        bytes memory stomach = 
            (patternDNA == 1 || patternDNA == 2 || patternDNA == 3 || patternDNA == 4 || patternDNA == 6 || patternDNA == 8 || patternDNA == 9 || patternDNA == 11 || patternDNA == 12 || patternDNA == 14 || patternDNA == 15 || patternDNA == 16 || patternDNA == 17 || patternDNA == 18 || patternDNA == 20 || patternDNA == 23 || patternDNA == 25 || patternDNA == 27) ? STOMACH_PATTERN : 
            (patternDNA == 22 || patternDNA == 30 || patternDNA == 31 || patternDNA == 32 || patternDNA == 33 || patternDNA == 34 || patternDNA == 35) ? SEPARATOR_STOMACH_PATTERN :
            bytes("");

        stomach = stomach.length != 0 ? abi.encodePacked(STOMACH_PATTERN_PREFIX, stomach, '\
</g>') : bytes("");

        bytes memory foot = 
            (patternDNA == 4 || patternDNA == 12 || patternDNA == 17 || patternDNA == 20 || patternDNA == 25 || patternDNA == 34) ? SOCKS_FOOT_PATTERN :
            (patternDNA == 27 || patternDNA == 28 || patternDNA == 29 || patternDNA == 30) ? VITILIGO_FOOT_PATTERN :
            bytes("");

        foot = foot.length != 0 ? abi.encodePacked(FOOT_PATTERN_PREFIX, foot, '\
</g>') : bytes("");

        return abi.encodePacked(
            tail,
            tail2,
            ear,
            face, 
            neck,
            stomach,
            foot
        );
    }

    function _getCoatColors(uint256 dnaColoring, uint256 dnaPattern) internal pure returns (string memory base, string memory pattern) {
        (string memory baseColor, string memory patternColor) = 
               (dnaColoring == 0) ? ("#FFF", "#FFF") : // Milk
               (dnaColoring == 1 || dnaColoring == 13) ? (STONE, GRAY) : // Stone & Silver
               (dnaColoring == 2) ? (COYOTE, (dnaPattern == 14 ? COYOTE_DARK : COYOTE_LIGHT)) : // Coyote
               (dnaColoring == 3) ? (INK, "#FFF") : // Ink
               (dnaColoring == 4) ? (SKY, "#FFF") : // Sky
               (dnaColoring == 5) ? (BLUSH, "#FFF") : // Blush
               (dnaColoring == 6) ? (POTION, POTION_LIGHT) : // Potion
               (dnaColoring == 7) ? (BEAR, COYOTE) : // Bear 
               (dnaColoring == 8) ? (MARTIAN, MARTIAN_LIGHT) : // Martian
               (dnaColoring == 9) ? (NAVY, NAVY_LIGHT) : // Navy
               (dnaColoring == 10) ? (INK, STONE) : // Moon
               (dnaColoring == 11) ? (STAR, "#FFF") : // Star
               (dnaColoring == 12) ? (ARMY, ARMY_LIGHT) : // Army
               (dnaColoring == 14) ? ("#FFF", INK) : // Cow
               (dnaColoring == 15) ? (CORAL, CORAL_LIGHT) : // Coral
               (dnaColoring == 16) ? (JADE, STAR) : // Jade
               (dnaColoring == 17) ? (FOX, "#FFF") : // Fox
               (ENERGY, "#FFF"); // Energy

        return ((dnaPattern == 15 || dnaPattern == 16 || dnaPattern == 17) ? (patternColor, baseColor) : (baseColor, patternColor)); // If flipped, return colors in reverse
    }

    function _getEyeColor(uint256 eyeDNA) internal pure returns (string memory) {
        return (eyeDNA == 0 || eyeDNA == 5) ? YELLOW :
               (eyeDNA == 1) ? SKY :
               (eyeDNA == 2 || eyeDNA == 6) ? BLUE :
               (eyeDNA == 3 || eyeDNA == 7) ? GRAY :
               GREEN;
    }

    function _getGemColor(uint256 gemDNA) internal pure returns (string memory) {
        return (gemDNA == 0) ? SAPPHIRE :
               (gemDNA == 1) ? EMERALD :
               RUBY;
    }
}"
    },
    "lib/openzeppelin-contracts/contracts/token/ERC721/ERC721.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.20;

import {IERC721} from "./IERC721.sol";
import {IERC721Metadata} from "./extensions/IERC721Metadata.sol";
import {ERC721Utils} from "./utils/ERC721Utils.sol";
import {Context} from "../../utils/Context.sol";
import {Strings} from "../../utils/Strings.sol";
import {IERC165, ERC165} from "../../utils/introspection/ERC165.sol";
import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC-721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
abstract contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Errors {
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    mapping(uint256 tokenId => address) private _owners;

    mapping(address owner => uint256) private _balances;

    mapping(uint256 tokenId => address) private _tokenApprovals;

    mapping(address owner => mapping(address operator => 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_;
    }

    /// @inheritdoc IERC165
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /// @inheritdoc IERC721
    function balanceOf(address owner) public view virtual returns (uint256) {
        if (owner == address(0)) {
            revert ERC721InvalidOwner(address(0));
        }
        return _balances[owner];
    }

    /// @inheritdoc IERC721
    function ownerOf(uint256 tokenId) public view virtual returns (address) {
        return _requireOwned(tokenId);
    }

    /// @inheritdoc IERC721Metadata
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /// @inheritdoc IERC721Metadata
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /// @inheritdoc IERC721Metadata
    function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
        _requireOwned(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string.concat(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 overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /// @inheritdoc IERC721
    function approve(address to, uint256 tokenId) public virtual {
        _approve(to, tokenId, _msgSender());
    }

    /// @inheritdoc IERC721
    function getApproved(uint256 tokenId) public view virtual returns (address) {
        _requireOwned(tokenId);

        return _getApproved(tokenId);
    }

    /// @inheritdoc IERC721
    function setApprovalForAll(address operator, bool approved) public virtual {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /// @inheritdoc IERC721
    function isApprovedForAll(address owner, address operator) public view virtual returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /// @inheritdoc IERC721
    function transferFrom(address from, address to, uint256 tokenId) public virtual {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists
        // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here.
        address previousOwner = _update(to, tokenId, _msgSender());
        if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /// @inheritdoc IERC721
    function safeTransferFrom(address from, address to, uint256 tokenId) public {
        safeTransferFrom(from, to, tokenId, "");
    }

    /// @inheritdoc IERC721
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual {
        transferFrom(from, to, tokenId);
        ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     *
     * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the
     * core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances
     * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by
     * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`.
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @dev Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted.
     */
    function _getApproved(uint256 tokenId) internal view virtual returns (address) {
        return _tokenApprovals[tokenId];
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in
     * particular (ignoring whether it is owned by `owner`).
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) {
        return
            spender != address(0) &&
            (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender);
    }

    /**
     * @dev Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner.
     * Reverts if:
     * - `spender` does not have approval from `owner` for `tokenId`.
     * - `spender` does not have approval to manage all of `owner`'s assets.
     *
     * WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this
     * assumption.
     */
    function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual {
        if (!_isAuthorized(owner, spender, tokenId)) {
            if (owner == address(0)) {
                revert ERC721NonexistentToken(tokenId);
            } else {
                revert ERC721InsufficientApproval(spender, tokenId);
            }
        }
    }

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * NOTE: the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that
     * a uint256 would ever overflow from increments when these increments are bounded to uint128 values.
     *
     * WARNING: Increasing an account's balance using this function tends to be paired with an override of the
     * {_ownerOf} function to resolve the ownership of the corresponding tokens so that balances and ownership
     * remain consistent with one another.
     */
    function _increaseBalance(address account, uint128 value) internal virtual {
        unchecked {
            _balances[account] += value;
        }
    }

    /**
     * @dev Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner
     * (or `to`) is the zero address. Returns the owner of the `tokenId` before the update.
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that
     * `auth` is either the owner of the token, or approved to operate on the token (by the owner).
     *
     * Emits a {Transfer} event.
     *
     * NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
     */
    function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) {
        address from = _ownerOf(tokenId);

        // Perform (optional) operator check
        if (auth != address(0)) {
            _checkAuthorized(from, auth, tokenId);
        }

        // Execute the update
        if (from != address(0)) {
            // Clear approval. No need to re-authorize or emit the Approval event
            _approve(address(0), tokenId, address(0), false);

            unchecked {
                _balances[from] -= 1;
            }
        }

        if (to != address(0)) {
            unchecked {
                _balances[to] += 1;
            }
        }

        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        return from;
    }

    /**
     * @dev Mints `tokenId` and transfers it 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 to, uint256 tokenId) internal {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner != address(0)) {
            revert ERC721InvalidSender(address(0));
        }
    }

    /**
     * @dev Mints `tokenId`, transfers it to `to` and checks for `to` acceptance.
     *
     * 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 to, uint256 tokenId) internal {
        _safeMint(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 to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        ERC721Utils.checkOnERC721Received(_msgSender(), address(0), to, tokenId, data);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal {
        address previousOwner = _update(address(0), tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(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 {
        if (to == address(0)) {
            revert ERC721InvalidReceiver(address(0));
        }
        address previousOwner = _update(to, tokenId, address(0));
        if (previousOwner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        } else if (previousOwner != from) {
            revert ERC721IncorrectOwner(from, tokenId, previousOwner);
        }
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients
     * are aware of the ERC-721 standard 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 like {safeTransferFrom} in the sense that it invokes
     * {IERC721Receiver-onERC721Received} on the receiver, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `tokenId` token must exist and be owned by `from`.
     * - `to` cannot be the zero address.
     * - `from` cannot be the zero address.
     * - 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) internal {
        _safeTransfer(from, to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeTransfer-address-address-uint256-}[`_safeTransfer`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is
     * either the owner of the token, or approved to operate on all tokens held by this owner.
     *
     * Emits an {Approval} event.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address to, uint256 tokenId, address auth) internal {
        _approve(to, tokenId, auth, true);
    }

    /**
     * @dev Variant of `_approve` with an optional flag to enable or disable the {Approval} event. The event is not
     * emitted in the context of transfers.
     */
    function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual {
        // Avoid reading the owner unless necessary
        if (emitEvent || auth != address(0)) {
            address owner = _requireOwned(tokenId);

            // We do not use _isAuthorized because single-token approvals should not be able to call approve
            if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) {
                revert ERC721InvalidApprover(auth);
            }

            if (emitEvent) {
                emit Approval(owner, to, tokenId);
            }
        }

        _tokenApprovals[tokenId] = to;
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Requirements:
     * - operator can't be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        if (operator == address(0)) {
            revert ERC721InvalidOperator(operator);
        }
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned).
     * Returns the owner.
     *
     * Overrides to ownership logic should be done to {_ownerOf}.
     */
    function _requireOwned(uint256 tokenId) internal view returns (address) {
        address owner = _ownerOf(tokenId);
        if (owner == address(0)) {
            revert ERC721NonexistentToken(tokenId);
        }
        return owner;
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../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.
 *
 * The initial owner is set to the address provided by the deployer. 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;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling 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 {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _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);
    }
}
"
    },
    "lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract 

Tags:
ERC721, ERC165, Multisig, Non-Fungible, Upgradeable, Multi-Signature, Factory|addr:0x097c3037f0fc4f34854c20006a2fcb2c30835bb2|verified:true|block:23605990|tx:0xa081b769b1935f48ce3312ac8af58c190a6eae7774298e839edab2ed37345d7d|first_check:1760808700

Submitted on: 2025-10-18 19:31:41

Comments

Log in to comment.

No comments yet.