Ape Curtis Testnet

Contract Diff Checker

Contract Name:
GobsDataSource

Contract Source Code:

File 1 of 1 : GobsDataSource

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

/**
 * @title GobsDataSource
 * @notice Stores RGBA pixel data and generates SVGs for on-chain Goblin images.
 */
contract GobsDataSource {
    address payable internal deployer;
    bool private contractSealed = false;
    string internal constant SVG_HEADER = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 29 29">';
    string internal constant SVG_FOOTER = '</svg>';
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";


    struct Trait {
        string traitType;
        string value;
    }

    struct GobData {
        bytes pixelData; // Row-major RGBA data (29x29 pixels, 3364 bytes)
        Trait[] traits;  // Array of traits for the token
    }

    

    mapping(uint256 => GobData) private gobData; // Stores data for each Goblin

    modifier onlyDeployer() {
        require(msg.sender == deployer, "Only deployer.");
        _;
    }

    modifier unsealed() {
        require(!contractSealed, "Contract is sealed.");
        _;
    }

    constructor() {
        deployer = payable(msg.sender);
    }

    /**
     * @notice Seal the contract to prevent further modifications.
     */
    function sealContract() external onlyDeployer unsealed {
        contractSealed = true;
    }

    /**
     * @notice Store RGBA pixel data for a Goblin.
     * @param tokenId The ID of the Goblin (0 <= tokenId < 2222).
     * @param pixelData The RGBA data (row-major order, 29x29 pixels, 3364 bytes).
     */
    function storePixelData(uint256 tokenId, bytes memory pixelData) external onlyDeployer unsealed {
        require(tokenId < 2222, "Invalid tokenId");
        require(pixelData.length == 29 * 29 * 3, "Invalid pixel data length");
        gobData[tokenId].pixelData = pixelData;
    }

   function storeTraits(uint256 tokenId, Trait[] memory traits) external onlyDeployer {
        delete gobData[tokenId].traits; // Clear existing traits
        for (uint256 i = 0; i < traits.length; i++) {
            gobData[tokenId].traits.push(traits[i]);
        }
    }


    // Retrieve traits for a token
    function getTraits(uint256 tokenId) external view returns (Trait[] memory) {
        require(gobData[tokenId].traits.length > 0, "Traits not set");
        return gobData[tokenId].traits;
    }
    /**
     * @notice Retrieve RGBA pixel data for a Goblin.
     * @param tokenId The ID of the Goblin.
     * @return The RGBA pixel data.
     */
    function getGobData(uint256 tokenId) external view returns (bytes memory) {
        require(tokenId < 2222, "Invalid tokenId");
        require(gobData[tokenId].pixelData.length > 0, "Pixel data not set");
        return gobData[tokenId].pixelData;
    }

    /**
     * @notice Generate the SVG for a Goblin from its pixel data.
     * @param tokenId The ID of the Goblin.
     */
    function getGobSVG(uint256 tokenId) external view returns (string memory svg) {
        require(tokenId < 2222, "Invalid tokenId");
        bytes memory pixels = gobData[tokenId].pixelData;
        require(pixels.length > 0, "Pixel data not set");

        svg = string(abi.encodePacked(SVG_HEADER));
        bytes memory buffer = new bytes(7);
        for (uint y = 0; y < 29; y++) {
            for (uint x = 0; x < 29; x++) {
                uint p = (y * 29 + x) * 3;
                for (uint i = 0; i < 3; i++) {
                    uint8 value = uint8(pixels[p + i]);
                    buffer[i * 2 + 1] = _HEX_SYMBOLS[value & 0xf];
                    value >>= 3;
                    buffer[i * 2] = _HEX_SYMBOLS[value & 0xf];
                }
                svg = string(abi.encodePacked(svg,
                    '<rect x="', toString(x), '" y="', toString(y),
                    '" width="1" height="1" shape-rendering="crispEdges" fill="#', string(buffer), '"/>'));
            }
        }

        svg = string(abi.encodePacked(svg, SVG_FOOTER));
    }

    /**
     * @notice Convert a slice of bytes to a uint32.
     * @param data The byte array.
     * @param index The starting index.
     * @return The uint32 representation of the RGBA value.
     */
    function toUint32(bytes memory data, uint256 index) internal pure returns (uint32) {
        return uint32(
            uint8(data[index]) << 24 |
            uint8(data[index + 1]) << 16 |
            uint8(data[index + 2]) << 8 |
            uint8(data[index + 3])
        );
    }


    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}

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

Context size (optional):